mailnix / Failover policies

    Core concepts

    Failover policies

    A failover policy says "if my primary destination misbehaves, swap to a backup." It runs after the routing decision picks a primary the policy attaches a fallback chain to that primary.

    Policy shape

    Each policy is keyed by (user, primary_destination_id). The fields:

    • Primary destination id: the destination this policy attaches to. Set by the routing engine when a rule (or the default) picks this destination.
    • Fallback destination ids: an ordered list of backup destinations to try, in order, when the primary fails.
    • Triggers: which kinds of failure switch to the next destination. Independent toggles:
    • on_5xx: the provider responded with a 5xx status (server error). Default on.
    • on_rate_limit: the provider responded with 429 or its proprietary rate-limit signal. Default on.
    • on_timeout: the provider didn't respond within timeout_seconds. Default on.
    • timeout_seconds: how long to wait before declaring a timeout. Default 30s.
    • max_attempts: cap on total attempts across primary + fallbacks. Default 3.
    • Enabled (boolean): toggle without deleting.

    How it runs

    1. Routing picks the primary destination.
    2. We call the primary. If it succeeds (2xx), the trace gets provider_response with the success payload done.
    3. If the primary fails in a way that matches one of the configured triggers, the trace gets a failover_triggered event and we move to the first fallback in the chain.
    4. The next fallback is called the same way. If it succeeds, the trace ends with provider_response on the fallback.
    5. If we exhaust the chain (or hit max_attempts), the trace's final status is failed.

    Each attempt is a separate provider_attempt + provider_response pair on the trace. You can see the full chain on the trace detail page.

    Common patterns

    Cloud-primary, SMTP-fallback

    You normally send through SES, but your AWS account has rate limits you don't want customers to feel:

    primary:  ses-prod
    fallback: [smtp-hostpoint]
    triggers: on_5xx, on_rate_limit, on_timeout

    Two clouds

    You want resilience against a single provider's outage:

    primary:  postmark-prod
    fallback: [resend-prod, smtp-emergency]
    triggers: on_5xx, on_timeout
    max_attempts: 3

    The third hop (smtp-emergency) is a last-resort host that's always-on but has lower deliverability better than dropping the message entirely.

    Region-locked failover

    Inside a single provider, fail over between regions:

    primary:  ses-eu-central-1
    fallback: [ses-us-east-1]
    triggers: on_5xx, on_timeout
    on_rate_limit: false  (the same throttle bucket pointless)

    What failover doesn't do

    • It doesn't retry the same destination. If the primary times out, we go to the fallback, not back to the primary. Use the provider's own retry budget for that.
    • It doesn't restart after a delivery webhook bounces. Once the provider says "accepted," the trace is no longer in the failover state machine. Bounce webhooks arrive later and don't trigger a re-send.
    • It doesn't re-route on 4xx. A 4xx response means the provider rejected the request as invalid (bad from-address, malformed body) sending the same payload to the fallback would just fail again. The trace ends with the primary's failure.

    To configure failover, hit `/destinations/failover` or use the MCP server: email_failover_policy_set.