Class: Posthaste::HttpClient

Inherits:
Object
  • Object
show all
Defined in:
lib/posthaste/http_client.rb

Overview

Authentication, the retry policy, and turning a refusal into an exception.

The retry rules are ported from the Python and TypeScript SDKs rather than reinvented, so a Rails app and a Python worker hitting the same API behave the same way under the same throttle.

Constant Summary collapse

DEFAULT_MAX_RETRIES =
2
DEFAULT_MAX_RETRY_DELAY =
60.0
BASE_BACKOFF =

First backoff step, in seconds. Doubles per attempt, capped at 8s, with full jitter applied on top.

0.5
MAX_BACKOFF =
8.0

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key:, base_url: DEFAULT_BASE_URL, transport: nil, max_retries: DEFAULT_MAX_RETRIES, max_retry_delay: DEFAULT_MAX_RETRY_DELAY, open_timeout: 10, read_timeout: 30, user_agent: nil, sleeper: nil, randomizer: nil) ⇒ HttpClient

Returns a new instance of HttpClient.



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/posthaste/http_client.rb', line 83

def initialize(api_key:, base_url: DEFAULT_BASE_URL, transport: nil,
               max_retries: DEFAULT_MAX_RETRIES, max_retry_delay: DEFAULT_MAX_RETRY_DELAY,
               open_timeout: 10, read_timeout: 30, user_agent: nil,
               sleeper: nil, randomizer: nil)
  @api_key = api_key.to_s
  @base_url = base_url.to_s.sub(%r{/+\z}, '')
  @transport = transport || NetHttpTransport.new(open_timeout: open_timeout,
                                                 read_timeout: read_timeout)
  @max_retries = max_retries
  @max_retry_delay = max_retry_delay
  @user_agent = user_agent || self.class.default_user_agent
  # Injected so the retry tests do not actually wait, and so the jitter is
  # deterministic under test without the policy itself knowing it is a test.
  @sleeper = sleeper || ->(seconds) { sleep(seconds) }
  @randomizer = randomizer || -> { Kernel.rand }
end

Class Method Details

.default_user_agentObject



100
101
102
103
# File 'lib/posthaste/http_client.rb', line 100

def self.default_user_agent
  mailer = defined?(::ActionMailer::VERSION::STRING) ? ::ActionMailer::VERSION::STRING : 'none'
  "posthaste-rails/#{Posthaste::VERSION} ruby/#{RUBY_VERSION} actionmailer/#{mailer}"
end

Instance Method Details

#inspectObject Also known as: to_s

Never prints the key. See Posthaste::Redaction for why this matters.



120
121
122
123
# File 'lib/posthaste/http_client.rb', line 120

def inspect
  "#<Posthaste::HttpClient base_url=#{@base_url.inspect} " \
    "api_key=#{Redaction.describe_key(@api_key).inspect} max_retries=#{@max_retries}>"
end

#send_email(payload, idempotent:) ⇒ Object

POST /v1/emails.

Two success statuses, and they mean different things:

202 {"status":"queued"}    — accepted, a new message exists.
200 {"status":"duplicate"} — an idempotency replay. No new message was
                           created; the id is the original's.

idempotent: is the whole retry rule. Without an idempotency key a retry after a lost response sends the email TWICE, so a send is only repeated automatically when the caller supplied one.



115
116
117
# File 'lib/posthaste/http_client.rb', line 115

def send_email(payload, idempotent:)
  decode(perform('post', '/v1/emails', payload, idempotent: idempotent))
end