Outbound Atlas

Atlas/The build plan/The engine

Sending architecture

How a cold email sequencer works under the hood: mailbox connectors, a per-inbox scheduler, reply and bounce ingestion, tracking, warmup, and what 100k inboxes actually cost to watch.

Build planmedium confidence14 minupdated 2026-10-0516 sources

A sequencer is not an email sender. It is a distributed mailbox client: thousands of real Google and Microsoft inboxes, each sending 10–30 messages a day on its own clock, each of which you must also read continuously to catch replies, bounces and out-of-office notes. Sending is the easy half. Ingestion — watching every inbox, threading every reply back to the right lead, and stopping the sequence within minutes — is where the engineering and the cloud bill live.

The second structural fact: the protocol you choose to connect mailboxes decides your regulatory burden. Reading a Google inbox via OAuth requires a restricted scope, which drags in Google's annual security assessment (see OAuth verification and the end of basic auth). App passwords avoid that, but only for as long as Google and Microsoft keep tolerating them.

The five subsystems

SubsystemJobHard part
ConnectorsAuthenticate and talk to each mailboxThree protocols, token refresh, provider quirks
SchedulerDecide which message leaves which inbox whenPer-inbox caps across 100k inboxes, time zones, idempotency
IngestionDetect replies, bounces, auto-repliesLong-lived connections or push subscriptions at scale
TrackingOpens, clicks, unsubscribesPer-customer tracking domains with TLS
WarmupInbox-to-inbox reputation networkLooking human to Google and Microsoft

Connectors: three ways into a mailbox

PathSendRead repliesAuthRegulatory cost
SMTP/IMAP + app passwordSMTP submissionIMAP poll or IDLEStatic app passwordNone from Google; Microsoft retiring Basic SMTP AUTH
SMTP/IMAP + OAuth (XOAUTH2)SMTPIMAPOAuth tokenGoogle: https://mail.google.com/ scope is restricted
Gmail APImessages.sendwatch() → Pub/Sub, history.listOAuthgmail.send is sensitive; any read scope is restricted
Microsoft GraphsendMailChange-notification subscriptionsOAuth (Entra app)Publisher verification; tenant consent

The details that bite:

So what

Build one internal Mailbox interface with three adapters (IMAP/SMTP, Gmail API, Graph) from day one. The protocol mix will shift under you: Microsoft's Basic SMTP AUTH goes default-off at the end of December 2026, and the cheapest inboxes in the market (private SMTP from Maildoso or Mailforge) are IMAP/SMTP-only.

The scheduler: per-inbox throttling at scale

The scheduler's job is to turn "campaign X, 5,000 leads, 3 steps" into a stream of individual send slots that respect:

  1. Per-inbox daily cap (e.g. 30), with ramp-up for new inboxes and a reduction when bounces rise.
  2. Minimum and randomised gap between sends from the same inbox (e.g. 8–15 minutes), so traffic looks human.
  3. Sending window in the prospect's or the sender's time zone, weekdays only.
  4. Campaign and workspace caps, plus Inbox rotation across the campaign's inbox pool.
  5. Step delays ("3 days after step 1 if no reply") and stop conditions (reply, bounce, unsubscribe, meeting booked).

A design that works to six figures of inboxes:

  • Materialise the next action per lead as a row: (lead_id, step_id, due_at, mailbox_id NULL). Index on due_at.
  • Keep per-mailbox state: sent_today, next_allowed_at, daily_cap, health. This is the token bucket.
  • A dispatcher loop pulls due rows, assigns each to an eligible mailbox whose next_allowed_at <= now, and enqueues a send job keyed by mailbox. Postgres SELECT … FOR UPDATE SKIP LOCKED or a Redis sorted set per shard is enough; you do not need Kafka at 3M emails/day (100k inboxes × 30).
  • Shard by mailbox, not by campaign. All work for one mailbox goes to one worker, which removes most race conditions on the per-inbox cap.

Idempotency is the bug that hurts customers. SMTP is not idempotent: if the connection drops after DATA but before the 250 reply, you do not know whether the message left. Retrying blindly double-sends to a prospect. Generate and persist the Message-ID before sending, mark the attempt as in_flight, and on ambiguous failure search the mailbox's Sent folder (IMAP SEARCH HEADER Message-ID, Gmail messages.list q=rfc822msgid:) before retrying.

Ingestion: replies, bounces, auto-replies

Getting notified

MethodLatencyScale costCaveats
IMAP pollingPoll interval100k inboxes at 60 s ≈ 1,667 logins or checks per secondSimple, works with any provider
IMAP IDLESecondsOne long-lived TLS socket per inbox per folderGmail caps at 15 simultaneous IMAP connections per account; sockets die silently
Gmail pushSecondsPub/Sub + history.listwatch() must be renewed at least every 7 days, daily recommended; max one notification/s per user; "in rare situations, notifications might be delayed or dropped"
Graph subscriptionsSecondsWebhook endpoint + renewalsMessage subscriptions expire after at most 10,080 minutes; 1,440 with resource data

Every push mechanism needs a reconciliation poll behind it, because both Google and Microsoft document dropped or delayed notifications. Run a slow sweep (every 15–30 minutes) that compares the last-seen historyId / UID / delta token with the server.

What 100k inboxes cost to watch on the Gmail API

Using Google's published quota costs (Gmail API quota page, as of October 2026: 1,200,000 units per minute per project; messages.send 100 units, watch 100, messages.get 20, history.list 2), my arithmetic for 100k Google inboxes:

ActivityAssumptionUnits/dayShare of daily project ceiling (1.728B)
Renew watch() daily100k × 10010M0.6%
Process inbound mail20 messages/inbox/day × (2 + 20)44M2.5%
Send campaign mail30/inbox/day × 100300M17%
Read the table carefully

Seventeen percent sounds safe, but sends cluster in business-hour windows. Squeeze 300M units into an 8-hour window and you are using roughly half of the per-minute ceiling — before warmup traffic, retries and reconciliation. At around 100k Google inboxes, a single Google Cloud project's Gmail quota becomes a capacity-planning constraint. These figures are my calculation from Google's published unit costs, not a measured benchmark.

On the IMAP side, 100k IDLE sockets is a memory and file-descriptor problem, not a CPU one. The commercial reference point is EmailEngine, whose vendor says "several thousand" mailboxes run on one instance before you shard, each shard with its own Redis. Plan for dozens of IMAP worker nodes at 100k inboxes, with health checks that reconnect silent sockets.

Threading replies to leads

Store the Message-ID of every message you send. An incoming message is a reply to a sequence if its In-Reply-To or References header contains one of your IDs. Fall back to provider thread IDs (Gmail threadId, Graph conversationId) and then to sender-address matching for clients that strip headers.

Follow-ups must thread too. Gmail only adds a message to an existing thread when the threadId is set, References and In-Reply-To comply with RFC 2822, and the Subject matches. Get this wrong and step 2 arrives as a new, unrelated email — which hurts reply rates and looks like spam.

Classifying what came in

TypeSignalsAction
Hard bouncemultipart/report; report-type=delivery-status per RFC 3464; Status: 5.x.x, Action: failedStop lead, suppress address, count against inbox health
Soft bounce4.x.x, mailbox full, greylistingRetry later, cap retries
Auto-reply / OOOAuto-Submitted: auto-replied (RFC 3834), X-Autoreply, Precedence: auto_reply, return date in bodyPause and resume after the date
Human replyEverything elseStop sequence, route to Unified inbox (Unibox), classify intent

When you send through Gmail or Microsoft 365, every bounce is asynchronous: the provider accepts the message and later drops a DSN into the sender's inbox. Your bounce detection is therefore part of the reply-ingestion pipeline, not the send path. Only private SMTP (Postfix, KumoMTA) gives you synchronous RCPT TO rejections. Bounce rates feed Bounce protection and inbox health scores.

Intent classification (interested, not now, wrong person, unsubscribe) is now an LLM call. At Claude Haiku 4.5's $1/$5 per million tokens (October 2026), an 800-token classification costs under a tenth of a cent — see Build costs and team and AI reply agent.

Tracking

Opens are a 1×1 image URL unique per message; clicks are links rewritten to a redirect that logs and forwards. Unsubscribe is a signed link plus a List-Unsubscribe header. Two engineering requirements:

  • Custom tracking domains per customer. Sharing one tracking hostname across all customers ties everyone's reputation together. Each customer CNAMEs track.theirdomain.com to your edge; you issue TLS certificates on demand (ACME). See Custom tracking domains.
  • Open data is noisy. Image proxies and privacy features pre-fetch pixels, so open rates overstate engagement. Many senders now disable open tracking on cold email; see Content and tracking.

Warmup network engineering

Warmup is a cross-tenant service: every connected inbox that opts in becomes both a sender and a receiver in a shared pool. The engine:

  1. Schedules low volumes of inbox-to-inbox mail between pool members, matched across providers (Google to Microsoft and back) — see ESP matching.
  2. On the receiving side, uses the reading connector to find warmup messages, move them out of spam, mark them important, open them and reply.
  3. Tags warmup messages (a filter string or header) so the network can find and hide them from the user.

It is a graph-scheduling problem with an adversary: Google and Microsoft can see the pattern. Pool size and diversity are the moat — a new entrant's pool is small and homogeneous. See Warmup networks, Built-in warmup and Does warmup work?.

Multi-tenancy and data model

Minimum viable schema:

  • workspace (tenant; agencies need sub-workspaces → Workspaces, roles and SSO, White-label and agency portals)
  • mailbox (provider, auth type, encrypted credentials, daily cap, health, warmup state)
  • domain (SPF/DKIM/DMARC status, tracking domain)
  • campaign, step, variant (for A/B testing and Spintax and message variants)
  • lead, campaign_lead (state machine: queued → active → replied/bounced/unsubscribed/finished)
  • message (Message-ID, thread ID, mailbox, direction, classification)
  • event (sent, opened, clicked, bounced, replied — append-only, partitioned by month)
  • suppression (workspace-level and global)

Rules that matter more than the schema:

  • Encrypt credentials with envelope encryption (a KMS-held key per tenant). You are holding the keys to thousands of business inboxes; a leak is existential.
  • Isolate noisy neighbours. One customer's 20k-inbox import must not starve everyone's IMAP workers. Per-tenant concurrency limits on connectors.
  • Global suppression across tenants is a liability and deliverability question; decide it deliberately (see Platform liability: what the sequencer itself risks).
  • EU data residency is a selling point for a German founder; storing replies means storing personal data (see GDPR and ePrivacy).
Gap in the record

No incumbent (Instantly, Smartlead, lemlist, EmailBison) has published an engineering blog post describing its scheduler, ingestion architecture or warmup network. Everything above is reconstructed from provider documentation, help-centre setup guides and first principles, not from vendor engineering disclosures.

Not verified

Microsoft Graph's Outlook throttling limits (commonly cited as 10,000 requests per 10 minutes per app per mailbox, four concurrent requests) could not be confirmed from Microsoft's throttling page in this pass. Verify before sizing Graph workers.

What this means for an entrant

  • Spend your first engineering months on ingestion, not sending. Reply detection that stops sequences within a minute and threads correctly is what customers notice; a send loop is a week of work.
  • Design for three connectors and a reconciliation poll from day one. Push notifications from Google and Microsoft are documented as lossy, and Microsoft's Basic SMTP AUTH default-off at the end of December 2026 will force OAuth on Microsoft inboxes (see OAuth verification and the end of basic auth).
  • Make sends idempotent before you have customers. Persist the Message-ID first and check Sent before retrying; a double-send to a prospect is the bug that gets you churned and screenshotted.
  • Budget Gmail API quota as a real constraint around 100k Google inboxes per Cloud project, and treat the restricted-scope assessment as the price of using the API at all.
  • Do not try to out-warm Instantly on day one. Its pool is the moat; a newcomer should integrate partner infrastructure and placement testing first (see Infrastructure strategy: build or partner, Inbox placement testing).
16 sources cited on this page · 9 domains
  1. the scope for IMAP, POP and SMTP access is https://mail.google.com/ developers.google.com
  2. restricted developers.google.com
  3. Smartlead help centre helpcenter.smartlead.ai
  4. Instantly help centre help.instantly.ai
  5. means accepted, not delivered learn.microsoft.com
  6. Microsoft docs learn.microsoft.com
  7. 2,000 messages a day per user, 500 on trial accounts knowledge.workspace.google.com
  8. 10,000 recipients a day and 30 messages a minute learn.microsoft.com
  9. 15 simultaneous IMAP connections per account support.google.com
  10. at least every 7 days, daily recommended developers.google.com
  11. expire after at most 10,080 minutes; 1,440 with resource data learn.microsoft.com
  12. Gmail API quota page developers.google.com
  13. EmailEngine emailengine.app
  14. the threadId is set, References and In-Reply-To comply with RFC 2822, and the Subject matches developers.google.com
  15. RFC 3464 rfc-editor.org
  16. Claude Haiku 4.5's $1/$5 per million tokens platform.claude.com