Automatic fallback when a provider fails

If the primary provider refuses or times out, the message is immediately retried through the next provider in the account's chain instead of failing.

What it is

Each adapter returns one of three verdicts. 'sent' ends the walk. 'skipped' means the adapter has nothing to work with (no phone number, no connected CRM contact) and the chain moves on quietly. 'failed' means the provider was reached and rejected it, which also moves on but is recorded as a real error. Every direct-send adapter carries a 15-second timeout so a silent provider surfaces as a failure the chain can fall past rather than a hang.

Also called: backup email sender · failover · second provider · message still goes out

See it
Automatic fallback when a provider fails
Area
Integrations
Group
Provider architecture
System
Provider Independence
Solves
3 named problems
01The port walks the resolved chain in priority order.
02Mailgun and Resend both abort at 15 seconds (AbortSignal.timeout) so a hung provider cannot hold the request open.
03A network exception marks the result retryable; an HTTP rejection does not, because the provider saw the message and retrying risks a duplicate.
Animate a message hitting provider one (red X), sliding to provider two (green tick), with the elapsed timer capping at 15s. Rendered nowhere directly — illustrate the chain from src/lib/providers/registry.ts. Sample data — no customer information appears here.
How it works
  1. 1The port walks the resolved chain in priority order.
  2. 2Mailgun and Resend both abort at 15 seconds (AbortSignal.timeout) so a hung provider cannot hold the request open.
  3. 3A network exception marks the result retryable; an HTTP rejection does not, because the provider saw the message and retrying risks a duplicate.
  4. 4If nothing sends and the last failure was retryable, the send is enqueued in message_outbox.
  5. 5If every adapter skipped, the caller gets skipped:true so the UI can say 'connect an integration' instead of showing a hard error.
Why we built it

The Resend adapter's header records what happens when a fallback is not really a fallback: it used to delegate back into the dispatcher that called it, so 'the call graph closed into a loop... until the stack overflowed. It was invisible for weeks because Mailgun sits ahead of Resend in the _platform chain and normally answers first. The moment Mailgun failed or was unconfigured, the fallback didn't fall back — it crashed the whole Node process, so the request never returned any response at all and the caller hung forever. That is what left "Sending…" spinning on the invite form.'

The problem
  • A provider outage silently dropped customer messages.
  • A hung provider held the request open until the platform's function timeout killed it with no response.
  • A crashed fallback path made an outage look like an app bug.
Sound familiar?
What you get
Messages keep going out through a second provider during an outage.
Failures surface as a named provider error instead of a spinner.
No duplicate customer messages, because only provably-unreached sends are retried.

See it on your own jobs

Twenty minutes, your numbers, no slide deck. We’ll build one of your real buildings in front of you and send you the estimate link at the end — yours to keep either way.

or keep browsing features →