Skip to main content

Guides

Errors & retries

One normalised PostboiError across every provider, plus opt-in retries.


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.

try {
	await mail.send({ to: 'bad@email', body: 'test' })
} catch (error) {
	if (mail.is_error(error)) {
		// error is a PostboiError
		console.error(error.provider) // e.g. "resend"
		console.error(error.status) // HTTP status, when applicable
		console.error(error.code) // provider-specific code, when available
		console.error(error.message) // normalised message
		console.error(error.raw) // the original provider payload
	} else {
		console.error(error)
	}
}
try {
	await mail.send({ to: 'bad@email', body: 'test' })
} catch (error) {
	if (mail.is_error(error)) {
		// error is a PostboiError
		console.error(error.provider) // e.g. "resend"
		console.error(error.status) // HTTP status, when applicable
		console.error(error.code) // provider-specific code, when available
		console.error(error.message) // normalised message
		console.error(error.raw) // the original provider payload
	} else {
		console.error(error)
	}
}

Common codes

Beyond provider-specific codes passed through on .code, postboi itself uses a few:

Code Meaning
skipped / spam The send was intentionally cancelled: a before.send hook or the honeypot.
no_provider / no_token / unknown_provider / missing_env Zero-config resolution failed. See Quick start. In development a missing credential logs instead of throwing.
cancel_not_supported cancel() on a provider without a cancellation API.
webhooks_not_supported receive() for a provider that emits no delivery events.
invalid_payload A webhook body that doesn’t parse.

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:

// postboi.config.ts
import { config } from 'postboi'

export default config({
	timeout: 30000, // per-request timeout in ms (default 30000)
	retries: 2, // retries on 429/5xx and network errors (default 0)
	retry_delay: 500 // base backoff in ms, doubles each attempt (default 500)
})
// postboi.config.ts
import { config } from 'postboi'

export default config({
	timeout: 30000, // per-request timeout in ms (default 30000)
	retries: 2, // retries on 429/5xx and network errors (default 0)
	retry_delay: 500 // base backoff in ms, doubles each attempt (default 500)
})

Retries fire on 429, 5xx, and network errors. The delay doubles each attempt (exponential backoff).

Retries are off by default on purpose. Retrying a send that already reached the provider can deliver a duplicate email, so enable retries only alongside an idempotency_key (where the provider supports it).