Error handling
Every provider throws the same normalised PostboiError on failure: HTTP errors,
provider error envelopes, timeouts, and network failures all funnel through it. Error
handling is identical no matter which provider you use, and the original provider payload is
kept on .raw.
Why it was refused
reason says why a provider refused, the same words whichever provider it was: auth, rate_limited, sender, account, recipient, compliance or request. It comes from
the provider’s own error code where postboi knows it (Twilio’s, AWS’s), and from the HTTP
status otherwise. It is never set on an unknown outcome, since nothing was refused.
The first four are about the provider rather than the message, so another provider might
accept the same message; that is what an SMS or WhatsApp provider chain fails over on, and fails_over(error) is the same test. A chain that ran out of providers
throws a FailoverError (still a PostboiError) whose errors lists every answer.
Common codes
Beyond provider-specific codes passed through on .code, postboi itself uses a few:
Webhook verification failures throw a WebhookVerificationError (a PostboiError subclass) with codes invalid_signature, missing_secret, stale_timestamp or unsupported_runtime. Return a 401 when you catch one.
Retries
Every provider accepts retries, retry_delay, and timeout:
Nothing is retried until retries is above 0. Then what gets retried depends on what
the failure proves. The delay doubles each attempt
(exponential backoff), and a Retry-After from the provider (in seconds or as a date) is
honoured up to a minute. When the retries run out, or none were asked for, the error’s retry_after says what the provider asked for, so a queue of your own can wait it out.
A 5xx or a dropped connection doesn’t mean the message didn’t go. The provider may have
queued it before answering badly or not at all, so sending it again can deliver it twice.
That is why those retries wait for a key the provider deduplicates on, and why the error
says outcome_unknown: true when they run out: look the message up with the provider
before you send it again yourself.
Providers that honour a key: the Postboi provider, Resend (a batch too), AhaSend, Lettermint, JetEmail, Loops, Primitive, Klaviyo (unique_id), HubSpot
(sendId) and Azure (a UUID, as its Operation-Id; a batch’s per-recipient keys aren’t
UUIDs, so a batch through Azure isn’t keyed). A failed read (a GET) is never outcome_unknown, since it can’t have sent anything. None of the SMS providers documents one for sending, Twilio included, so
a text is never retried after a 5xx.
If a duplicate is better than a drop for you, retry_unknown: true retries those failures
without a key too.
Retries are off by default on purpose. Set
retriesand pass anidempotency_keyon sends to a provider that honours one.