Skip to content
Writing
NewMulti-TenantB2B SaaSDeliverabilityArchitectureSadaSend

Multi-Tenant Email Infrastructure for B2B SaaS: Complete Customer Isolation with SadaSend

How to build enterprise B2B email architecture where one bad tenant cannot destroy deliverability for your other customers. Complete isolation patterns using SadaSend.

Tayyab MughalFounder & AI Chief8 min read

The "Bad Neighbor" dilemma in multi-tenant SaaS email

In a multi-tenant B2B SaaS, your customers send email through your platform: onboarding invites, transactional alerts, monthly invoices, and automated notifications. If all tenants share a single sending domain or a single IP address, you have introduced an existential single point of failure: the Bad Neighbor Problem.

If Tenant A uploads a stale, scraped contact list and triggers a 2.8% spam complaint rate or a 9% hard bounce rate, mailbox providers like Google and Yahoo throttle the reputation of the shared domain and sending IP. Suddenly, Tenant B's password reset links and Tenant C's enterprise invoice emails land in the spam folder or get delayed by hours.

Traditional email APIs like SendGrid or Mailchimp make true tenant isolation prohibitively expensive and architecturally clumsy. SendGrid forces you into subuser accounts with separate $89.95/month dedicated IPs, manual API key provisioning, and zero built-in automated circuit breakers.

SadaSend was engineered specifically for modern B2B SaaS architectures. With native tenant scoping, dynamic custom return-paths, automated DKIM selector generation, and real-time algorithmic circuit breakers, SadaSend guarantees that no single customer can ever compromise your platform's deliverability.

The 3 architectural tiers of B2B email tenancy

Every B2B SaaS platform evolves through three stages of email architecture. Choosing the right tier balances implementation velocity against reputation risk.

  • Tier 1 is unacceptable for any revenue-generating B2B SaaS: spam complaints against one tenant directly punish your core transactional domain.
  • Tier 2 allows immediate onboarding without requiring customer DNS setup: SadaSend provisions isolated subdomains and distinct DKIM keys on the fly.
  • Tier 3 provides complete white-label enterprise compliance: your customers verify CNAME records pointing to SadaSend, achieving complete DMARC alignment and custom branding.
Tenancy TierDomain / Identity StrategyReputation IsolationComplexity & Cost
Tier 1: Shared DomainAll tenants send from noreply@saasapp.comNone. One bad tenant burns all other tenants.Low complexity, catastrophic risk.
Tier 2: Tenant Subdomainstenant.notifications.saasapp.com with custom Return-PathHigh. Mailbox providers calculate domain reputation per subdomain.Medium. Automated DNS provisioning via SadaSend API.
Tier 3: White-Label BYO DomainCustomer brings app.customer.com with verified CNAMEsComplete. 100% tenant-owned reputation and branding.Zero-maintenance using SadaSend automated domain verification.

Domain and IP isolation: Return-Path vs. From header alignment

Under DMARC (RFC 7489), an email passes authentication if either SPF or DKIM is aligned with the From: header domain.

In a multi-tenant environment, the From: header might be billing@customer.com or alerts@tenant.saasapp.com. If the envelope sender (Return-Path) is a generic shared address like bounces@sendgrid.net, SPF fails alignment, and bounce processing becomes a tangled mess.

SadaSend solves this through dynamic envelope isolation: each tenant receives a dedicated return-path subdomain (bounces.tenant123.saasapp.com or bounces.customer.com) mapped to an isolated DKIM selector (sadasend._domainkey.tenant123.saasapp.com).

When a bounce or spam complaint occurs, the receiving MTA transmits the DSN (Delivery Status Notification) directly to the tenant's isolated return-path, enabling instant, automated tenant-level telemetry without cross-tenant contamination.

Algorithmic circuit breakers: Token-bucket rate limiting and automated quarantine

Tenant isolation requires two layers of defense: proactive rate limiting to prevent sudden outbound bursts, and reactive circuit breakers that quarantine misbehaving tenants before mailbox providers take action.

Proactive Rate Limiting: Implement a distributed token-bucket algorithm keyed by tenant_id. Free or trial tenants might be capped at 10 emails/minute with a burst capacity of 50, while enterprise tenants have custom throughput allocations.

Reactive Circuit Breakers: Listen to SadaSend delivery webhooks. If a tenant's metrics cross these critical thresholds within any rolling 1-hour window, automatically trip the circuit breaker:

  • Spam Complaint Rate >= 0.1%: Issue an automated warning and restrict non-transactional broadcast campaigns.
  • Spam Complaint Rate >= 0.3%: Hard quarantine. Suspend all outbound sending for this tenant immediately.
  • Hard Bounce Rate >= 5.0%: Trip circuit breaker. Force tenant list verification before resuming.

Production implementation: Multi-tenant dispatch engine in TypeScript

Here is a complete, production-ready multi-tenant dispatch service written in TypeScript. It integrates Redis token-bucket rate limiting, verifies tenant status, dynamically signs with tenant-specific credentials, and dispatches via the SadaSend API.

TYPESCRIPT
// lib/email/multi-tenant-dispatcher.ts
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');

interface TenantEmailConfig {
  tenantId: string;
  plan: 'free' | 'pro' | 'enterprise';
  status: 'active' | 'quarantined' | 'suspended';
  customDomain?: string; // e.g. "mail.customer.com"
  subdomain: string;    // e.g. "tenant-42.notifications.saasapp.com"
  hourlyLimit: number;
}

interface SendEmailPayload {
  to: string;
  subject: string;
  html: string;
  text: string;
  metadata?: Record<string, string>;
}

export class MultiTenantEmailDispatcher {
  private apiKey: string;
  private apiBase: string;

  constructor() {
    this.apiKey = process.env.SADASEND_API_KEY!;
    this.apiBase = 'https://api.sadasend.com/v1';
  }

  /**
   * Token-bucket rate limiter enforcing tenant limits in Redis.
   */
  private async checkRateLimit(tenantId: string, limitPerHour: number): Promise<boolean> {
    const key = `ratelimit:email:${tenantId}:${Math.floor(Date.now() / 3600000)}`;
    const count = await redis.incr(key);
    if (count === 1) {
      await redis.expire(key, 3600); // 1 hour TTL
    }
    return count <= limitPerHour;
  }

  /**
   * Dispatches email with strict tenant isolation and fallback handling.
   */
  async dispatch(tenant: TenantEmailConfig, payload: SendEmailPayload) {
    // 1. Enforce automated quarantine circuit breaker
    if (tenant.status === 'quarantined' || tenant.status === 'suspended') {
      throw new Error(`Tenant ${tenant.tenantId} is quarantined due to elevated bounce/complaint rates.`);
    }

    // 2. Enforce tenant token-bucket rate limits
    const allowed = await this.checkRateLimit(tenant.tenantId, tenant.hourlyLimit);
    if (!allowed) {
      throw new Error(`Tenant ${tenant.tenantId} exceeded hourly dispatch quota of ${tenant.hourlyLimit}.`);
    }

    // 3. Resolve From address: use custom domain if verified, otherwise fallback to isolated subdomain
    const sendingDomain = tenant.customDomain || tenant.subdomain;
    const fromAddress = `notifications@${sendingDomain}`;

    // 4. Dispatch payload via SadaSend API with tenant scoping
    const response = await fetch(`${this.apiBase}/emails`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json',
        'X-SadaSend-Tenant-Id': tenant.tenantId, // Isolated telemetry & subaccount attribution
      },
      body: JSON.stringify({
        from: fromAddress,
        to: payload.to,
        subject: payload.subject,
        html: payload.html,
        text: payload.text,
        headers: {
          'X-Entity-Ref-ID': `tenant-${tenant.tenantId}-${Date.now()}`,
        },
        tags: [
          `tenant:${tenant.tenantId}`,
          `plan:${tenant.plan}`,
        ],
        metadata: {
          ...payload.metadata,
          tenantId: tenant.tenantId,
        },
      }),
    });

    if (!response.ok) {
      const errorData = await response.json().catch(() => ({}));
      throw new Error(`SadaSend dispatch failed (${response.status}): ${JSON.stringify(errorData)}`);
    }

    const data = await response.json();
    return {
      messageId: data.id,
      tenantId: tenant.tenantId,
      status: 'queued',
    };
  }
}

Ingesting tenant webhooks and triggering automated quarantines

To protect your platform, your webhook consumer must process bounce and complaint events in real time.

For an in-depth breakdown of resilient webhook ingestion with cryptographic signature validation and Dead Letter Queues (DLQs), see our comprehensive guide on email webhook architecture (/blog/email-webhook-architecture-signatures-dlq).

Below is the event handler that listens for SadaSend delivery telemetry, tracks per-tenant rolling metrics, and triggers automatic quarantine if complaints exceed 0.3%:

TYPESCRIPT
// lib/email/webhook-quarantine-handler.ts
import Redis from 'ioredis';

const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');

interface SadaSendWebhookEvent {
  id: string;
  type: 'email.delivered' | 'email.bounced' | 'email.complained';
  timestamp: string;
  data: {
    messageId: string;
    recipient: string;
    tenantId: string;
    bounceType?: 'hard' | 'soft';
    complaintReason?: string;
  };
}

export async function handleTenantWebhookEvent(event: SadaSendWebhookEvent) {
  const { tenantId, bounceType } = event.data;
  if (!tenantId) return;

  const hourWindow = Math.floor(Date.now() / 3600000);
  const baseKey = `metrics:tenant:${tenantId}:${hourWindow}`;

  if (event.type === 'email.delivered') {
    await redis.hincrby(baseKey, 'delivered', 1);
    await redis.expire(baseKey, 7200);
  } else if (event.type === 'email.complained') {
    await redis.hincrby(baseKey, 'complaints', 1);
    await redis.expire(baseKey, 7200);
  } else if (event.type === 'email.bounced' && bounceType === 'hard') {
    await redis.hincrby(baseKey, 'hard_bounces', 1);
    await redis.expire(baseKey, 7200);
  }

  // Calculate rolling error ratios
  const metrics = await redis.hgetall(baseKey);
  const delivered = parseInt(metrics.delivered || '0', 10);
  const complaints = parseInt(metrics.complaints || '0', 10);
  const hardBounces = parseInt(metrics.hard_bounces || '0', 10);
  const total = delivered + complaints + hardBounces;

  if (total >= 50) {
    const complaintRate = complaints / total;
    const bounceRate = hardBounces / total;

    // Hard quarantine: Spam complaints >= 0.3%
    if (complaintRate >= 0.003) {
      await quarantineTenant(tenantId, `Spam complaint rate reached ${(complaintRate * 100).toFixed(2)}%`);
    }
    // Hard quarantine: Bounces >= 5.0%
    else if (bounceRate >= 0.05) {
      await quarantineTenant(tenantId, `Hard bounce rate reached ${(bounceRate * 100).toFixed(2)}%`);
    }
  }
}

async function quarantineTenant(tenantId: string, reason: string) {
  console.warn(`🚨 QUARANTINE TRIGGERED for Tenant ${tenantId}: ${reason}`);
  // Update database status to 'quarantined' and alert customer success team
  // e.g. await db.tenants.update({ where: { id: tenantId }, data: { emailStatus: 'quarantined' } });
}

Why engineering teams choose SadaSend over legacy providers

Trying to graft multi-tenancy onto legacy providers like SendGrid or Mailchimp creates massive operational friction:

  • SendGrid Subusers require manual provisioning, separate credential management, and expensive dedicated IP pools ($89.95/mo each) to achieve any real isolation. See our comparison in Mandrill vs SendGrid (/blog/mandrill-vs-sendgrid) and our step-by-step guide on migrating from SendGrid to SadaSend (/blog/migrating-from-sendgrid).
  • Mailchimp / Mandrill forces all transactional emails into rigid Mandrill subaccounts tied to a single legacy Mailchimp marketing billing plan.
  • SadaSend provides native, first-class tenant scoping via simple headers (X-SadaSend-Tenant-Id), programmatic DNS domain provisioning via REST API, automated DKIM/SPF verification webhooks, and sub-10ms global dispatch latency.
Early Access

Building AI agents that send email?

Join the SadaSend early access waitlist to get scoped API keys, recipient allowlists, and Model Context Protocol (MCP) servers upon launch.

Use Case:
One email when it opens
Social Hashtags & Share
#EmailAPI#DeveloperTools#MultiTenant#B2BSaaS#EmailDeliverability#DKIM
Tayyab MughalFounder & AI Chief

Building SadaSend — transactional email with an MCP server that has a ceiling. Writes about deliverability, email infrastructure, and what happens when you hand an autonomous agent a sending credential.