Markdown Editor

Email Delivery System Design Template

Transactional email platform: message classes, per-destination queues, RFC 5321 retries, SPF/DKIM/DMARC, IP warm-up, bounces, one-click unsubscribe

सिस्टम डिज़ाइनपूर्णRFC 5321; RFC 6376; RFC 7208; RFC 9989 (DMARC, May 2026); RFC 8058designemaildeliverymath
एडिटर में खोलें.md डाउनलोड करें

उपयोग कैसे करें: Use it when designing or reviewing a platform that sends transactional email for many tenants or products. Fill the message classes and the recipient provider mix in section 3 first, because provider limits and warm-up time, not servers, set the ceiling. The main pitfall is one shared queue and IP pool, where one tenant's bad list or one provider's deferrals delay every password reset.

प्रीव्यू

System Design: [Email delivery platform name]

Reference design for a multi-tenant transactional email platform: an API that accepts messages, per-destination queues and SMTP delivery (RFC 5321), sender authentication with SPF (RFC 7208), DKIM (RFC 6376) and DMARC (RFC 9989, May 2026, which obsoletes RFC 7489), IP and domain reputation, bounce and complaint processing, suppression lists and one-click unsubscribe (RFC 8058). It follows the structure of the System Design Document template, adds a 45-minute interview path, access-pattern and failure-mode tables and a cited "How real systems do it" box, and is pre-filled with a worked design to adapt. Replace every [bracketed] value and every row marked Example; values marked Example are assumptions, not benchmarks. Section 2.4 names email marketing laws as dated examples; this is a design format, not legal advice.

Document control

FieldValue
Document ID[SDD-EM-001]
System / feature[Transactional email delivery]
Version[0.1]
Status[Draft / In review / Approved / Implemented / Superseded]
Owner[@owner]
Author(s)[@author]
Reviewers[@reviewer-1], [@reviewer-2]
Last updatedYYYY-MM-DD
Classification[Internal]
DifficultyAdvanced
Standards implemented[RFC 5321, RFC 5322, RFC 6376, RFC 7208, RFC 9989, RFC 8058, RFC 3463, RFC 3464, RFC 8461]
Related[Notification service design], [Tenant onboarding policy], [ADR: shared versus dedicated IP pools]

Approvals

RoleNameDecisionDate
Tech lead[@name][Approved / Changes requested]YYYY-MM-DD
Staff / principal engineer[@name][Decision]YYYY-MM-DD
Deliverability lead[@name][Decision]YYYY-MM-DD
Security[@name][Decision]YYYY-MM-DD
Privacy / legal reviewer[@name][Decision]YYYY-MM-DD

Revision history

VersionDateAuthorChange summary
0.1YYYY-MM-DD[@name]First draft for design review
0.2YYYY-MM-DD[@name]Example: Split one-time codes into their own queue class and IP pool
0.3YYYY-MM-DD[@name]Example: Updated DMARC references from RFC 7489 to RFC 9989
1.0YYYY-MM-DD[@name]Example: Approved

Contents

1. Overview

1.1 Problem statement

[Example: Product teams send password resets, receipts and account notices through a shared SMTP relay. Last quarter one tenant imported an old address list, the bounce rate on the shared IPs passed 9% in an hour, and a large mailbox provider deferred all our mail for six hours. Password reset codes arrived after they had expired, support tickets tripled, and nobody could tell which messages had been delivered, bounced or were still queued.]

1.2 Proposed solution

[Example: Tenants send through an HTTPS API that validates, deduplicates and stores each message, then places it on a queue by message class. A scheduler groups messages by destination (mailbox provider and MX host) and releases them at the rate each destination accepts for each sending IP. Delivery agents sign with the tenant's DKIM key and speak SMTP with TLS. Replies are classified into delivered, deferred (retry) and bounced (suppress). Feedback loops and one-click unsubscribe requests update a suppression list that every send checks first. One-time codes use a separate high-priority queue and IP pool with short expiry. Reputation is measured per tenant, per IP and per sending domain, and a tenant that crosses bounce or complaint thresholds is paused automatically before it harms others.]

1.3 Decisions requested from reviewers

IDQuestionRecommended optionSection
Q-01Example: Build our own MTA fleet or relay through a provider?Own delivery agents on an open-source MTA core, with a provider as overflow6, 10
Q-02Example: Shared or dedicated IPs?Shared pools by class and reputation tier; dedicated pool only above a steady volume7.5
Q-03Example: Retry policy?Per class: codes expire in 15 min, receipts and notices retry for 5 days, subscribed mail for 2 days7.3
Q-04Example: Who owns the From domain?Tenant domain with delegated DKIM selector and aligned return path7.4
Q-05Example: Automatic tenant pause thresholds?Bounce 5% or complaints 0.3% over a 24-hour window7.5
[Q-nn][Question][Option][Section]

1.4 45-minute interview path

Use this order when the document serves an interview or a short design review. The written sections below are the long form of each step.

MinutesCoverOutput on the whiteboard
0 to 5Scope: transactional versus bulk, message classes, tenants, below-the-line items (2.3)Class table with priority and expiry
5 to 10Estimates: messages per second at peak, concurrent SMTP sessions, storage, IPs needed per provider (section 3)Four numbers, "provider limits, not CPU, set the ceiling" consequence
10 to 15Interface: POST /v1/messages with idempotency key, delivery events (section 4)One call, one event stream
15 to 25High-level design: API, store, class queues, destination scheduler, delivery agents, feedback processing (section 6)One diagram
25 to 40One deep dive: per-destination scheduling and retries (default, 7.2 and 7.3), or reputation and warm-up (7.5), or authentication and alignment (7.4)Options, choice, reason
40 to 45Failures (provider defers everything, DKIM key leak), the 10x row in section 10Two failure rows

Start the default deep dive with the naive answer, one FIFO queue and a pool of workers that send as fast as they can. Show what happens when one large provider starts deferring: the workers fill with retries for that provider and a password reset for a different provider waits behind them. Then move to per-destination queues with token-bucket release and per-class priority.

2. Requirements

2.1 Core functional requirements

IDRequirementPriority
FR-01Example: Accept a message by API with an idempotency key; return a message IDMust
FR-02Example: Render stored templates with tenant data; store the rendered MIME for retriesMust
FR-03Example: Sign every message with DKIM for the tenant's domain; align SPF return path where possibleMust
FR-04Example: Deliver over SMTP with opportunistic TLS, and enforce TLS where the destination publishes MTA-STSMust
FR-05Example: Retry temporary failures per class policy; stop on permanent failuresMust
FR-06Example: Suppress addresses after hard bounce, complaint or unsubscribe; check before every sendMust
FR-07Example: One-click unsubscribe headers on subscribed and promotional classesMust
FR-08Example: Emit delivery events (accepted, sent, delivered, deferred, bounced, complained, unsubscribed) by webhook and APIMust
FR-09Example: Pause a tenant automatically above bounce or complaint thresholdsMust
FR-10Example: Scheduled send and per-tenant send rate limitsShould
[FR-nn][Requirement][Must / Should / Could]

2.2 Non-functional requirements

IDQualityTargetMeasured as
NFR-01Time to first attempt, codes classExample: p99 at or below 5 s from API acceptEvent timestamps
NFR-02Time to first attempt, other classesExample: p99 at or below 60 sEvent timestamps
NFR-03DurabilityExample: No accepted message lost; every message ends in delivered, bounced, expired or suppressedDaily reconciliation of terminal states
NFR-04API availabilityExample: 99.95% monthlySLO on POST /v1/messages
NFR-05Duplicate sendsExample: At most 1 in 100 000 messages delivered twiceMessage-ID audit on seed accounts
NFR-06ReputationExample: Complaint rate per provider below 0.10%, never reaching 0.30%Provider postmaster dashboards
NFR-07IsolationExample: One tenant's bad list cannot raise another tenant's bounce ratePer-tenant and per-pool metrics
[NFR-nn][Quality][Target][Measurement]

2.3 Below the line (out of scope)

Name these explicitly so reviewers and interviewers do not pull the design toward them.

  • [Example: campaign builder, audience segmentation and A/B testing (marketing product)]
  • [Example: inbound mail receiving and parsing, except bounce and feedback mailboxes]
  • [Example: SMS and push fallback (see Notification Service System Design (system-design/design-notification-service))]
  • [Example: open and click tracking pixels (off by default; separate privacy review)]
  • [Item deferred, with the release that will take it]

2.4 Legal and mailbox provider context (dated examples)

The platform carries mail for tenants, and tenants carry the legal duty for their content and consent. The platform still needs the mechanics that make compliance possible. The rules below are named as dated examples only; check the current official text and a qualified professional before relying on any of them. This is not legal advice.

SourceWhat the platform must make possibleChecked
United States: CAN-SPAM Act, FTC compliance guide (August 2023, penalty figure updated January 2024)A working opt-out in commercial mail, honoured within 10 business days and kept working for at least 30 days after sending; transactional or relationship messages are largely exempt but must not carry false routing information2026-10-30
United Kingdom: PECR regulation 22, ICO guidance on electronic mail marketingConsent or soft opt-in for marketing to individuals, a simple opt-out in every message, sender identity not concealed. The ICO says this guidance is under review after the Data (Use and Access) Act 20252026-10-30
European Union: Directive 2002/58/EC (ePrivacy), Article 13 on unsolicited communications, EUR-Lex (consolidated version of 19 December 2009)Prior consent for marketing email, with an existing-customer exception for similar products that requires a free, easy opt-out at collection and in each message; national laws implement the detail2026-10-30 (Publications Office consolidated text)
Google, Email sender guidelinesAll senders: SPF or DKIM, valid forward and reverse DNS, TLS, spam rate below 0.3%. Senders of more than 5 000 messages a day to Gmail accounts: SPF and DKIM and DMARC, From domain aligned, one-click unsubscribe for marketing and subscribed mail. Google advises keeping the spam rate below 0.10%2026-10-30
[Other mailbox providers your tenants reach][Their published sender rules][Date]

The design consequence is that unsubscribe, suppression, DKIM alignment and complaint tracking are platform features, not tenant options.

3. Capacity estimation

3.1 Assumptions

InputSymbolValueSource
Messages accepted per dayDDExample: 30 millionAPI metrics x growth
Peak-hour factor over the daily averageffExample: 3Hourly histogram
Short burst rate the API must absorbBBExample: 2 000 messages/sLargest tenant's reset storm
Average SMTP transaction time with connection reusettExample: 0.5 sDelivery agent timings
Average rendered MIME sizemmExample: 40 KBTemplate sizes
Events per message (accepted, sent, delivered and later events)eeExample: 5, at 300 bytes eachEvent schema
Share of mail to the largest providersAs_AExample: 40%Recipient domain mix
Messages per hour one warm IP may send to that providerripr_{ip}Example: 50 000Observed deferral point, not a published limit

3.2 Throughput

λˉ=D86 400=3×10786 400≈347 messages/s\bar{\lambda} = \frac{D}{86\,400} = \frac{3 \times 10^{7}}{86\,400} \approx 347 \ \text{messages/s}
Hpeak=D24×f=1.25×106×3=3.75×106 messages/h≈1 042 messages/sH_{peak} = \frac{D}{24} \times f = 1.25 \times 10^{6} \times 3 = 3.75 \times 10^{6} \ \text{messages/h} \approx 1\,042 \ \text{messages/s}

The API and queues are sized for the burst B=2 000B = 2\,000 messages/s. Delivery agents have session headroom for the burst (3.3), but destination limits are sized for the peak hour, so a burst above about 1 042 messages/s queues. The reset storm in BB is code class mail, which is released first, so NFR-01 holds; other classes behind a burst that lasts more than about a minute can miss NFR-02, which is accepted and tracked as queue age (9.3).

3.3 Concurrent SMTP sessions

By Little's law, sessions in flight equal arrival rate times time in the system:

L=λ×t=1 042×0.5≈521 sessions at peak hour,2 000×0.5=1 000 during a burstL = \lambda \times t = 1\,042 \times 0.5 \approx 521 \ \text{sessions at peak hour}, \qquad 2\,000 \times 0.5 = 1\,000 \ \text{during a burst}

So a small fleet of delivery agents (for example 8 agents at about 125 sessions each in the burst) is enough; the hard limit is how many sessions each destination accepts per IP, which the scheduler enforces (7.2).

3.4 Storage

DataCalculationResult
Rendered MIME kept for retries and support (3 days)3×107×40 KB×33 \times 10^{7} \times 40 \ \text{KB} \times 33.6 TB
Delivery events kept online (30 days)3×107×5×300 B×303 \times 10^{7} \times 5 \times 300 \ \text{B} \times 301.35 TB
Event write rate3×107×5/86 4003 \times 10^{7} \times 5 / 86\,400about 1 736 events/s average
Backlog if the largest provider defers everything for 1 hour at peak3.75×106×0.4×40 KB3.75 \times 10^{6} \times 0.4 \times 40 \ \text{KB}1.5 million messages, 60 GB

The MIME store is object storage with a 3-day lifecycle; the queue holds only message IDs and scheduling state, so a 60 GB backlog is a metadata problem of 1.5 million small rows, not a disk problem.

3.5 IPs and warm-up

Peak-hour mail to the largest provider is 3.75×106×0.4=1.5×1063.75 \times 10^{6} \times 0.4 = 1.5 \times 10^{6} messages/h. At the observed rip=50 000r_{ip} = 50\,000 messages/h per IP:

Nip=1.5×10650 000=30 warm IPs for that provider at peakN_{ip} = \frac{1.5 \times 10^{6}}{50\,000} = 30 \ \text{warm IPs for that provider at peak}

Daily, that provider receives 3×107×0.4=1.2×1073 \times 10^{7} \times 0.4 = 1.2 \times 10^{7} messages, or 400 000 per IP per day across 30 IPs. A new IP that starts at 1 000 messages a day and grows by half each day reaches that level on day 16 (table in 7.5). Consequence: IPs are provisioned and warmed weeks before the volume arrives, and the warm-up plan is part of capacity planning.

3.6 Summary

QuantityEstimateDesign consequence
Accept rate2 000 messages/s burst, 347/s averageStateless API, durable queue before any SMTP work
Delivery rateabout 1 042 messages/s at peak hourSized by destination limits, not by CPU
SMTP sessionsabout 520 at peak, 1 000 in a burstFew delivery agents; connection reuse per destination
Storage3.6 TB MIME, 1.35 TB eventsObject store with lifecycle; events in a time-partitioned store
IPs30 warm IPs for the largest providerPools per class; warm-up planned 16 or more days ahead

4. API design

4.1 Endpoints

Method and pathPurposeNotes
POST /v1/messagesAccept one messageRequires Idempotency-Key; returns 202 with message ID
POST /v1/messages/batchAccept up to 500 messages sharing a templateOne idempotency key per item
GET /v1/messages/{id}Current state and event historyFor support tools
GET /v1/suppressions?address=Check suppression statusTenant scope only
DELETE /v1/suppressions/{address}Remove a manual suppressionNever removes complaint or unsubscribe entries without recipient action
POST /v1/domainsRegister a sending domain; returns DNS records to publishDKIM selector, return-path, DMARC check
POST /u/{token}One-click unsubscribe endpoint (RFC 8058)No cookies, no login, no redirect

The Idempotency-Key request header follows the pattern of the IETF draft draft-ietf-httpapi-idempotency-key-header-08 (22 October 2026, an Internet-Draft, not an RFC). A repeated key with the same body returns the original message ID; with a different body it returns 422 with a problem details body (RFC 9457).

4.2 Request example

{"from": {"address": "receipts@mail.shop.example.com", "name": "Example Shop"}, "to": [{"address": "ana@example.net"}], "class": "receipt", "template_id": "order-receipt-v3", "data": {"order_id": "ORD-104233", "total": "49.90"}, "tags": ["orders"], "send_at": null}

Response:

{"message_id": "msg_01J9ZK7Q4R", "status": "accepted", "suppressed": false}

4.3 Headers the platform adds

The platform adds the list headers for subscribed classes and signs them with DKIM, because RFC 8058 requires the DKIM signature to cover both List-Unsubscribe and List-Unsubscribe-Post:

From: Example Shop <news@mail.shop.example.com>
Message-ID: <msg_01J9ZK7Q4R@mail.shop.example.com>
List-Unsubscribe: <https://u.mail.shop.example.com/u/9f2c7d1e>
List-Unsubscribe-Post: List-Unsubscribe=One-Click
DKIM-Signature: v=1; a=rsa-sha256; d=shop.example.com; s=em2026a;
  h=from:to:subject:date:message-id:list-unsubscribe:list-unsubscribe-post;
  bh=[base64 body hash]; b=[base64 signature]

4.4 Delivery events

Events go to the tenant's webhook (signed, retried) and to the event API. Each event carries the message ID, tenant ID, recipient hash, type, timestamp (RFC 3339), and for failures the SMTP reply code and enhanced status code (RFC 3463).

{"event_id": "evt_8812", "message_id": "msg_01J9ZK7Q4R", "type": "bounced", "bounce_type": "permanent", "smtp_code": 550, "enhanced_code": "5.1.1", "occurred_at": "2026-10-30T09:14:03Z"}

5. Data model

5.1 Entities

A message belongs to one tenant and has many delivery attempts; suppressions are scoped to a tenant, plus one global list for hard bounces.

5.2 Storage choices

DataStoreKeyWhy
Message metadata and stateExample: Sharded relational databasemessage_id; unique (tenant_id, idempotency_key)Transactions for state changes and dedupe
Rendered MIMEObject storage, 3-day lifecyclemime_refLarge, immutable, cheap
Scheduling queuesExample: Partitioned log plus in-memory destination queues in the schedulerClass and destinationOrdered release, replay after crash
Suppression listExample: Replicated key-value store(tenant_id, SHA-256 of normalised address) plus a global hard-bounce listOne read on every send, sub-millisecond
Delivery eventsTime-partitioned columnar store(tenant_id, day)Support search and analytics
DKIM private keysKey management serviceSelectorKeys never leave the signing boundary in plain form

5.3 Access patterns

Access patternFrequencyKey or index that serves itPartition key rationale
Deduplicate an incoming messageEvery accept (up to 2 000/s)Unique index (tenant_id, idempotency_key)Partition by tenant keeps the unique check on one shard
Check suppression before sendEvery send(tenant_id, address_hash) and global hard-bounce listHash of address avoids storing raw addresses in the hot path
Release next messages for a destinationContinuous, per destinationIn-memory queue per (class, destination group, IP pool) rebuilt from the logDestination, not tenant, is what is rate-limited
Record an attempt and its resultEvery attemptmessage_id + attempt_noSame shard as the message row
Show a message's history to supportRaremessage_id, events by message_idNo scan
Compute tenant bounce and complaint ratesEvery minuteStreaming counters by tenant and poolPre-aggregated, never a query on raw events

6. High-level design

ComponentResponsibilityScales by
Message APIAuthenticate tenant, validate, dedupe, suppress, render, store, enqueueStateless instances
Class queuesDurable ordered log per message classPartitions
Destination schedulerGroup by destination and IP pool, release at allowed rate, hold deferred mail until its retry timeShards by destination group
Delivery agentsSMTP sessions with connection reuse, TLS policy, DKIM signing callSessions per agent
Signing serviceDKIM signing with keys held in KMSStateless instances
Result classifierMap replies and async bounces to delivered, deferred, bouncedStream consumers
Reputation monitorRates per tenant, IP, domain and provider; automatic pauseStreaming aggregation
Webhook dispatcherSigned delivery of events to tenants with retriesSee Webhook Delivery System Design (system-design/design-webhook-delivery)

7. Detailed design

The deep dives in this section, each with options, choice and reason:

Deep diveOptions consideredChoiceReasonSection
SchedulingOne FIFO; per-tenant queues; per-destination queuesPer-destination queues within each class, released by token bucket per IPProvider limits are per destination and per IP7.2
RetriesFixed interval; exponential; per class and per replyPer class, per reply code, with RFC 5321 minimumsCodes need fast expiry; receipts need patience7.3
AuthenticationPlatform domain only; tenant domain with delegationTenant domain, delegated DKIM selector, aligned return pathDMARC alignment and tenant reputation separation7.4
ReputationOne shared pool; dedicated per tenant; tiered poolsPools by class and tenant reputation tierIsolation without wasting cold IPs7.5

7.1 Message classes

ClassExamplesPriorityExpiryIP poolOne-click unsubscribe
codeExample: login codes, password resetHighest15 minpool-codes (warm, low volume)No
receiptExample: order confirmation, invoiceHigh5 dayspool-txnNo
accountExample: security alerts, policy noticesHigh5 dayspool-txnNo
subscribedExample: newsletters, product updates the user signed up forNormal2 dayspool-bulk by tierYes

The class is declared by the tenant and checked: a receipt that is mostly promotion is a known abuse pattern, so content rules and complaint rates per class are monitored (9.2).

7.2 Destination scheduling

Mail is grouped by destination: the recipient domain is resolved to MX hosts and then mapped to a destination group (one large provider owns many domains and MX hosts). Each (class, destination group, sending IP) has a token bucket for messages per hour and a cap on concurrent sessions. The scheduler releases the oldest message whose bucket has a token, highest class first.

When a destination replies with a temporary failure that signals rate pressure, the scheduler halves the bucket rate for that IP and destination and raises it again by 10% every 15 minutes without deferrals (additive increase after multiplicative decrease, as in TCP congestion control). Codes never wait behind receipts, and a destination that defers everything slows only its own queue.

7.3 Retries and bounce classification

RFC 5321 (section 4.5.4.1) says a sender must wait before retrying a destination, that the retry interval should generally be at least 30 minutes, that the give-up time generally needs to be at least 4 to 5 days, and that experience favours two attempts in the first hour and then one every two or three hours. The design follows that for long-lived classes and expires short-lived ones instead of retrying.

ClassAttemptsGive-upResult after give-up
receipt, accountFirst attempt, then at 30 and 60 minutes, then every 2 hours: 62 attempts, the last at hour 119120 hours (5 days)expired event, tenant may resend through another channel
subscribedSame pattern48 hoursexpired; stale newsletters are worse than none
codeFirst attempt, then one retry after 5 minutes on a different IP only when the reply indicates a connection-level fault15 minutesexpired event so the application can offer another factor

Reply classification uses the basic code first and the enhanced status code (RFC 3463) second:

SignalExampleTreatment
2yz after DATA250Delivered (accepted by the receiving server, not necessarily the inbox)
4yz421 4.7.0, 451 4.3.0Deferred; retry per class; feed rate control
5yz with 5.1.x550 5.1.1 (bad mailbox)Hard bounce; suppress the address for all tenants on the global list
5yz with 5.7.x550 5.7.1 (policy or reputation)Block bounce; do not suppress the address; alert deliverability; check authentication and IP listing
Asynchronous bounceDelivery Status Notification (RFC 3464) to the return pathParse, match by the variable envelope return path token, treat as above

Some providers return 5yz for reputation blocks that are temporary in practice. Treating those as hard bounces would wipe valid addresses, which is why 5.7.x is kept separate.

7.4 Authentication and alignment

MechanismSpecificationWhat the platform does
SPFRFC 7208Return-path domain is a tenant subdomain (bounce.mail.shop.example.com) whose SPF record includes the platform's sending ranges
DKIMRFC 6376, crypto updates RFC 8301 and RFC 8463Signs with a 2048-bit RSA key per tenant domain and selector; the tenant publishes a CNAME for the selector so the platform can rotate keys; an Ed25519 signature may be added alongside
DMARCRFC 9989 (May 2026), reports in RFC 9990 and RFC 9991Platform checks that the tenant domain publishes a DMARC record and that DKIM d= and the From domain align; aggregate reports are parsed for the tenant's dashboard
MTA-STS and TLS reportingRFC 8461, RFC 8460Delivery agents fetch and cache destination MTA-STS policies and refuse plaintext delivery where a policy is in enforce mode

DMARC moved from RFC 7489 to RFC 9989 in May 2026. Among the changes, the pct tag is removed (RFC 9989 Appendix A.6), a DNS tree walk replaces the Public Suffix List for finding the organisational domain (section 4.10), and the np tag for non-existent subdomains, first defined in RFC 9091, is now part of the core specification. Review your record parsing and your tenants' DMARC checks against the new text.

Key rotation: each tenant domain has two selectors (for example em2026a and em2026b). The platform publishes the new key under the idle selector, waits for DNS TTL plus a margin, switches signing, and retires the old key after 7 days so mail in retry still verifies.

7.5 Reputation, IP pools and warm-up

Reputation lives at three levels: sending IP, sending domain, and the tenant that causes both. Pools separate classes and tenant tiers:

PoolTenantsPurpose
pool-codesAll tenants, codes class onlySmall, steady, never shares IPs with promotions
pool-txnGood-standing tenantsReceipts and account notices
pool-bulk-goodTenants with low complaint historySubscribed mail
pool-probationNew tenants and tenants recovering from a pauseContains damage while history builds
pool-dedicated-[tenant]Tenants above a steady volumeTheir reputation, their consequences

Automatic controls per tenant over a rolling 24-hour window (Example thresholds; set yours from provider guidance and history):

MetricWarnPause sendingExample for a tenant sending 200 000/day
Hard bounce rate2%5%Warn at 4 000 bounces, pause at 10 000
Complaint rate0.08%0.3%Warn at 160 complaints, pause at 600

Warm-up of a new IP (Example: start at 1 000 messages a day and grow by half each day until the 400 000/day target from 3.5; the excess goes through already warm IPs in the same pool):

Day1357911131516
Messages to the largest provider1 0002 2505 06211 39125 62957 665129 746291 929400 000 (cap)

Send the earliest warm-up volume to the most engaged recipients, watch deferrals per provider, and hold the step (do not grow) on any day with rising 4.7.x deferrals. Complaint feedback loops, where providers offer them, arrive in the Abuse Reporting Format (RFC 5965) and are treated as an unsubscribe for that tenant.

7.6 Suppression and one-click unsubscribe

Every send checks the suppression list before rendering. Entries come from hard bounces (global), complaints, unsubscribes and tenant uploads (tenant scope). Addresses are normalised (lower-case domain; local part kept as is, because RFC 5321 says it may be case-sensitive) and stored as hashes.

One-click unsubscribe follows RFC 8058: the message carries a List-Unsubscribe header with one HTTPS URL and a List-Unsubscribe-Post: List-Unsubscribe=One-Click header, both covered by the DKIM signature. The mail client sends a POST with that key-value body, without cookies or authorisation, and the endpoint must not redirect.

The token in the URL is an opaque, signed reference to (tenant, list, address hash); it never contains the address in clear text, and a replayed token is harmless because the operation is idempotent.

7.7 Duplicates and delivery semantics

SMTP delivery is at-least-once. If the connection drops after the final . of DATA but before the 250 reply arrives, the sender cannot know whether the receiver accepted the message (RFC 1047 describes this window), and a retry may duplicate it. The design reduces duplicates in three places: the idempotency key at accept time, a stable Message-ID across retries so receivers that dedupe can do so, and short SMTP timeouts on the DATA reply balanced against the risk of resending large messages. NFR-05 accepts a small residual rate.

7.8 How real systems do it

How real systems do it (paraphrased, not copied). The Amazon SES Developer Guide page "Warming up dedicated IP addresses (standard)" (checked 2026-10-30) explains that mailbox providers trust IPs with a history of good mail, that warm-up can take from about two to six weeks depending on the provider, and that early volume should go to the most active recipients while bounces and throttling are watched. SES warms new dedicated IPs automatically by splitting a customer's mail between the new IPs and IPs that are already warm (a shared pool for a first dedicated IP, the customer's existing dedicated IPs otherwise), shifting the share over 45 days on a time basis. After warm-up it suggests keeping roughly 1 000 messages a day per provider per IP to hold the reputation, and avoiding sudden jumps. The overflow-to-warm-IPs approach in 7.5 is the same idea. Read the original in 12.3; this box keeps only the general idea.

8. Scalability and performance

ConcernApproachLimit before redesign
Accept rateStateless API, partitioned queueExample: about 10 000 messages/s with current DB shards
Destination limitsMore warm IPs per pool, planned weeks aheadWarm-up time, not hardware
Scheduler stateShard by destination group; one large provider may need its own shardAbout 2 million queued messages per shard in memory
Suppression readsReplicated key-value store with local cache of recent hits[Limit]
Event volumeColumnar, time-partitioned, 30-day hot retention[Limit]

Latency budget for the code class (target p99 5 s to first attempt):

StepBudget
API accept, dedupe, suppression check, render, store150 ms
Queue to scheduler200 ms
Scheduler release (codes never wait for tokens below their reserved share)500 ms
SMTP session reuse or connect, TLS, DKIM signing1 500 ms
Headroom2 650 ms

9. Reliability, security and operations

9.1 Failure modes

ComponentFailureDetectionBlast radiusMitigationRTO
Large providerDefers all mail from one pool4yz share per destinationMail to that provider from that pool; 1.5 million messages per peak hour (3.4)Per-destination queues contain it; rate halves; codes move to pool-codes; deliverability on callExample: hours, set by the provider
Sending IPListed on a blocklistBlocklist monitor, 5.7.x bouncesOne IP's share of a poolRemove IP from rotation, investigate tenant, request delistingExample: 1 to 3 days
TenantImports a stale listBounce rate streamThat tenant and its poolAutomatic pause at 5% (7.5), probation pool on resumeExample: 5 min to pause
Signing service or KMSUnavailableSigning error rateAll outgoing mailHold mail in queue (never send unsigned); cached signing keys sealed in memory for 15 minExample: 15 min
DKIM keyPrivate key leakedUnexpected DMARC report sourcesOne tenant domainRotate to the idle selector, revoke the old recordExample: 1 h plus DNS TTL
Scheduler shardCrashHealth checkDestinations on that shardRebuild state from the log and message DBExample: 2 min
Unsubscribe endpointDownSynthetic POSTUnsubscribes fail; mail clients may fall back to marking as spamMulti-region, no dependency on the message DB for the writeExample: 5 min
[Component][Failure][Signal][Scope][Mitigation][Time]

9.2 Security, abuse and privacy

ThreatControl
Account takeover used to send phishingStrong tenant authentication, scoped API keys, new-tenant probation, content scanning for credential phishing
Sending as a domain the tenant does not ownDomain verification by DNS before any mail; DKIM d= only for verified domains
Header injection through template dataEncode all header values; reject CR and LF in data used in headers
Promotional mail sent as receipt to skip unsubscribeContent classifier and complaint rate per class; reclassification and warnings
Recipient data exposureAddress hashes in the hot path; MIME deleted after 3 days; events hold a recipient hash and domain
Tracking without consentOpen and click tracking off by default and outside this design (2.3)

9.3 Observability

SignalMetric or logAlert threshold
Time to first attemptp99 by classCodes above 5 s for 5 min
Deferral share4yz per destination and poolDoubling against the 7-day baseline
Bounce and complaint ratesPer tenant, pool, providerWarn thresholds in 7.5
Queue ageOldest message per class and destinationReceipts older than 1 h
Provider dashboardsPostmaster spam rate and domain reputationSpam rate at or above 0.10%
Terminal-state reconciliationAccepted minus terminal minus in flightAny non-zero gap older than 6 days

9.4 Cost estimate

ItemUnit costQuantityMonthly cost
Delivery agents and scheduler[$/instance-month]Example: 12[$]
Sending IPs[$/IP-month]Example: 60 across pools[$]
MIME object storage[$/GB-month]Example: 3.6 TB steady[$]
Event store[$/GB-month]Example: 1.35 TB hot[$]
Overflow relay provider[$/1 000 messages][Volume][$]
Total[$]

10. Trade-offs and alternatives

DecisionOptionsChosenWhyWhat we give up
DeliveryRelay all mail through a commercial provider; own MTA fleet; hybridOwn fleet with provider overflowControl of pools, retries and data; overflow covers warm-up gapsDeliverability operations become our job
Queue modelOne queue; per tenant; per destination per classPer destination per classMatches how providers limitMore scheduler state
IP strategyAll shared; all dedicated; tiered poolsTiered poolsIsolation where it matters, warm IPs used fullyPool moves need care
From domainPlatform domain; tenant domainTenant domain with delegationDMARC alignment, tenant owns its reputationOnboarding needs DNS work
Retry for codesStandard 5-day retry; short expiryShort expiry with eventA late code is uselessSome codes never arrive; app must offer another factor
What changes at 10x / 0.1x10x (300 million/day): about 300 warm IPs for the largest provider by the 3.5 formula, scheduler sharded per large provider, warm-up becomes a continuous programme. 0.1x (3 million/day): about 3 IPs per large provider, a single scheduler, and a commercial relay may cost less than running a fleetReview at each doubling of volumeIP reputation work grows first, not compute[Note]
[Decision][Options][Chosen][Why][Cost]

11. Delivery plan and risks

11.1 Milestones

MilestoneScopeExit criterionTarget date
M1API, store, suppression, relay through providerAll classes flow end to end; events reconciledYYYY-MM-DD
M2Own delivery agents for code class, warm-up of pool-codesCodes p99 at or below 5 s for 14 daysYYYY-MM-DD
M3Receipts and account notices on own fleetBounce and deferral rates at or below provider baselineYYYY-MM-DD
M4Subscribed class, one-click unsubscribe, reputation automationRFC 8058 test with seed accounts passes; automatic pause testedYYYY-MM-DD

11.2 Rollout and rollback

[Example: Move traffic per class and per destination group from the relay to the own fleet in steps of 10%, following the warm-up table. Rollback sends the class back to the relay by configuration within 5 minutes; messages already queued finish on whichever path holds them.]

11.3 Risks

IDRiskLikelihoodImpactMitigationOwner
R-01Example: A tenant's bad list damages a shared poolMediumHighProbation pool, automatic pause[@name]
R-02Example: Provider sender rules tighten againHighMediumQuarterly review of provider pages; authentication and unsubscribe already enforced[@name]
R-03Example: DMARC RFC 9989 parsing differences break domain checksMediumMediumTest suite with records in old and new syntax[@name]
R-04Example: Warm-up slower than the volume planMediumHighKeep relay overflow contract until pools reach target[@name]
[R-nn][Risk][L / M / H][L / M / H][Mitigation][@name]

12. Appendices

12.1 Open questions

IDQuestionOwnerDue
OQ-01Example: Do we offer dedicated IPs below 100 000 messages a day?[@name]YYYY-MM-DD
OQ-02Example: Which providers' feedback loops do we join first?[@name]YYYY-MM-DD
OQ-03Example: When RFC 5321bis is published, which changes affect the delivery agents?[@name]YYYY-MM-DD
[OQ-nn][Question][@name]YYYY-MM-DD

12.2 Glossary

TermMeaning
MTAMail transfer agent: software that relays mail over SMTP
MXDNS record naming the hosts that accept mail for a domain
Return pathEnvelope sender address where bounces go; the domain SPF checks
AlignmentDMARC check that the authenticated domain matches the visible From domain
DeferralTemporary 4yz refusal; retry later
Hard bouncePermanent failure for a bad address; suppress
Feedback loopProvider report sent when a recipient marks mail as spam
Warm-upGradual growth of volume on a new IP or domain to build reputation
[Term][Meaning]

12.3 References

ReferenceUse
RFC 5321 Simple Mail Transfer Protocol (October 2008); a successor (rfc5321bis) was in the RFC Editor queue on 2026-10-30Transfer, reply codes, retry guidance in 7.3
RFC 5322 Internet Message FormatHeaders, Message-ID
RFC 6376 DKIM Signatures (September 2011), with RFC 8301 and RFC 84637.4
RFC 7208 SPF (April 2014)7.4
RFC 9989 DMARC (May 2026), obsoletes RFC 7489 and RFC 9091; RFC 9990 aggregate reporting; RFC 9991 failure reporting7.4
RFC 8058 One-click unsubscribe (January 2017) and RFC 2369 list header fields4.3, 7.6
RFC 3463 Enhanced Mail System Status Codes; RFC 3464 Delivery Status Notifications; RFC 5965 feedback report format7.3, 7.5
RFC 8461 MTA-STS; RFC 8460 SMTP TLS Reporting7.4
Google, Email sender guidelines, checked 2026-10-302.4, NFR-06
Amazon SES Developer Guide, Warming up dedicated IP addresses (standard), checked 2026-10-307.8
Notification Service System Design (system-design/design-notification-service)Channel choice above this platform
Webhook Delivery System Design (system-design/design-webhook-delivery)Event delivery to tenants
Rate Limiter System Design (system-design/design-rate-limiter)Token buckets in 7.2
Distributed Message Queue System Design (system-design/design-message-queue)Class queues
Email Campaign Brief (marketing/email-campaign-brief)Consent and unsubscribe for the tenant's own campaigns
Back-of-Envelope Estimation and Capacity Model (system-design/back-of-envelope-estimation)Method for section 3

Standard and licence note

Based on the IETF specifications cited in 12.3 (RFC 5321, RFC 6376, RFC 7208, RFC 8058 and the DMARC RFCs 9989 to 9991, published May 2026) and on common industry practice for transactional email. The legal and mailbox provider rules in 2.4 are dated examples, not legal advice; check the current official text and a qualified professional. The Amazon SES page is paraphrased in 7.8 only; no text was copied, and all volumes, thresholds and warm-up figures are illustrative examples. Original text written for this collection. Template licence: MIT.

संबंधित टेम्पलेट