← Back to blog
email failoverreliabilityinfrastructure

How to Build Email Failover for Your SaaS (Step-by-Step Guide)

Jasper Van Moose·June 15, 2026·5 min read

The Reality of Email Provider Failures

Your authentication emails are more fragile than you think. Most SaaS applications treat email as a fire-and-forget operation, but when your primary email provider goes down, password resets fail, signup confirmations vanish, and users can't access your application.

The problem runs deeper than obvious outages. Email providers can silently reject messages, throttle your sends without warning, or face deliverability issues that leave your emails in spam folders. Many popular email APIs are essentially wrappers around a single cloud provider — if that underlying service fails, the wrapper fails too.

Email failover isn't optional for production SaaS applications. It's infrastructure reliability 101.

For the business case on *why* this matters — before the how — see What Happens When Your Email Provider Goes Down?.

Architecture: Multi-Provider Email System

Building email redundancy requires more than just having backup API keys. You need intelligent routing, failure detection, and automatic recovery. Here's the foundational architecture:

interface EmailProvider {

id: string;

priority: number;

healthScore: number;

lastFailure?: Date;

consecutiveFailures: number;

}

class EmailFailoverSystem {

private providers: EmailProvider[] = [

{ id: 'primary', priority: 1, healthScore: 100, consecutiveFailures: 0 },

{ id: 'secondary', priority: 2, healthScore: 95, consecutiveFailures: 0 },

{ id: 'tertiary', priority: 3, healthScore: 90, consecutiveFailures: 0 }

];

async sendWithFailover(email: EmailMessage): Promise<DeliveryResult> {

const availableProviders = this.getHealthyProviders();

for (const provider of availableProviders) {

try {

const result = await this.attemptSend(provider, email);

this.recordSuccess(provider);

return result;

} catch (error) {

this.recordFailure(provider, error);

continue;

}

}

throw new Error('All email providers failed');

}

}

This basic structure handles provider selection and failure tracking, but production email failover needs significantly more sophistication.

Circuit Breaker Implementation

Circuit breakers prevent cascading failures by automatically removing unhealthy providers from rotation. When a provider hits a failure threshold, the circuit opens and requests route around it.

class CircuitBreaker {

private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';

private failureCount = 0;

private lastFailureTime?: Date;

async execute<T>(operation: () => Promise<T>): Promise<T> {

if (this.state === 'OPEN') {

if (this.shouldAttemptReset()) {

this.state = 'HALF_OPEN';

} else {

throw new Error('Circuit breaker is OPEN');

}

}

try {

const result = await operation();

this.onSuccess();

return result;

} catch (error) {

this.onFailure();

throw error;

}

}

private onFailure(): void {

this.failureCount++;

this.lastFailureTime = new Date();

if (this.failureCount >= 5) { // 5 failures in 60 seconds

this.state = 'OPEN';

}

}

}

The key insight: failures cluster. If a provider fails once, it's likely to fail again soon. Circuit breakers protect your application from waiting for timeouts on providers that are clearly having issues.

Stream Isolation for Failover

Not all emails are equal. Your password reset emails deserve different failover logic than your marketing campaigns. Stream isolation ensures critical transactional emails get priority routing and dedicated provider capacity.

enum EmailStream {

TRANSACTIONAL = 'transactional',

MARKETING = 'marketing',

OUTREACH = 'outreach'

}

interface StreamConfig {

stream: EmailStream;

requireFallback: boolean;

maxLatency: number;

minHealthScore: number;

allowedProviders: string[];

}

const streamConfigs: Record<EmailStream, StreamConfig> = {

[EmailStream.TRANSACTIONAL]: {

stream: EmailStream.TRANSACTIONAL,

requireFallback: true,

maxLatency: 5000,

minHealthScore: 95,

allowedProviders: ['primary', 'secondary', 'tertiary']

},

[EmailStream.MARKETING]: {

stream: EmailStream.MARKETING,

requireFallback: false,

maxLatency: 30000,

minHealthScore: 80,

allowedProviders: ['secondary', 'tertiary']

}

};

Transactional emails get the highest quality providers and mandatory fallback. Marketing emails can tolerate higher latency and lower health scores. This prevents your newsletter from impacting password reset reliability.

Health Scoring and Provider Selection

Smart routing requires continuous provider health assessment. Simple up/down checks aren't enough — you need nuanced scoring based on latency, success rates, and deliverability signals.

class ProviderHealthMonitor {

calculateHealthScore(provider: EmailProvider, window: TimeWindow): number {

const metrics = this.getMetrics(provider, window);

const successRate = metrics.successful / metrics.total;

const avgLatency = metrics.totalLatency / metrics.successful;

const bounceRate = metrics.bounces / metrics.delivered;

// Weighted scoring

const reliabilityScore = successRate * 50;

const latencyScore = Math.max(0, 30 - (avgLatency / 100));

const deliverabilityScore = Math.max(0, 20 - (bounceRate * 100));

return Math.min(100, reliabilityScore + latencyScore + deliverabilityScore);

}

}

Health scores should factor in both technical reliability (does the API respond?) and email deliverability (do messages reach inboxes?). A provider that accepts your emails but delivers them to spam has effectively failed.

Retry Logic with Exponential Backoff

Email failover isn't just about switching providers — it's about intelligent retry behavior. Messages that fail should retry with increasing delays, and dead-letter queues should capture messages that exhaust all retry attempts.

interface RetryConfig {

maxAttempts: number;

delays: number[]; // [30s, 2m, 8m, 30m, 2h]

shouldRetry: (error: Error) => boolean;

}

class DurableEmailQueue {

async enqueue(message: EmailMessage, attempt = 1): Promise<void> {

if (attempt > this.retryConfig.maxAttempts) {

await this.moveToDeadLetter(message);

return;

}

const delay = this.retryConfig.delays[attempt - 1] || 7200000; // 2h max

setTimeout(async () => {

try {

await this.emailSystem.sendWithFailover(message);

} catch (error) {

if (this.retryConfig.shouldRetry(error)) {

await this.enqueue(message, attempt + 1);

} else {

await this.moveToDeadLetter(message);

}

}

}, delay);

}

}

Regional Considerations

Multi-provider routing becomes more complex when you operate across regions. EU-based SaaS applications need providers that can guarantee data residency, while global applications need regional provider selection for latency optimization.

Your failover system should understand provider geography and route messages accordingly. A US-based backup provider might not be suitable for EU customer data, even during an outage.

Monitoring and Incident Detection

The best email failover systems detect issues before they impact users. Monitor provider response times, success rates, and deliverability metrics across all streams and regions.

Set up alerting for circuit breaker state changes, health score degradation, and retry queue depth. When a provider starts failing, you want to know immediately — not when users start complaining about missing emails.

Getting Started

Building production-grade email failover takes months of engineering time and ongoing operational overhead. You need provider relationships, health monitoring, intelligent routing, retry systems, and 24/7 incident response.

Most teams should focus on their core product instead of reimplementing email infrastructure. Truncus provides multi-provider email failover as a service — deterministic delivery, circuit breakers, stream isolation, and EU-only processing — so you can ship features instead of debugging email delivery.

Your emails should always deliver.

Multi-provider failover, synchronous delivery confirmation, EU-first routing. Try Truncus free.

How to Build Email Failover for Your SaaS (Step-by-Step Guide) — Truncus Blog