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
| Subsystem | Job | Hard part |
|---|---|---|
| Connectors | Authenticate and talk to each mailbox | Three protocols, token refresh, provider quirks |
| Scheduler | Decide which message leaves which inbox when | Per-inbox caps across 100k inboxes, time zones, idempotency |
| Ingestion | Detect replies, bounces, auto-replies | Long-lived connections or push subscriptions at scale |
| Tracking | Opens, clicks, unsubscribes | Per-customer tracking domains with TLS |
| Warmup | Inbox-to-inbox reputation network | Looking human to Google and Microsoft |
Connectors: three ways into a mailbox
| Path | Send | Read replies | Auth | Regulatory cost |
|---|---|---|---|---|
| SMTP/IMAP + app password | SMTP submission | IMAP poll or IDLE | Static app password | None from Google; Microsoft retiring Basic SMTP AUTH |
| SMTP/IMAP + OAuth (XOAUTH2) | SMTP | IMAP | OAuth token | Google: https://mail.google.com/ scope is restricted |
| Gmail API | messages.send | watch() → Pub/Sub, history.list | OAuth | gmail.send is sensitive; any read scope is restricted |
| Microsoft Graph | sendMail | Change-notification subscriptions | OAuth (Entra app) | Publisher verification; tenant consent |
The details that bite:
- Google OAuth over IMAP is not a loophole. Google states the scope for IMAP, POP and SMTP access is
https://mail.google.com/, which Google classifies as restricted, alongsidegmail.readonly,gmail.modifyand evengmail.metadata. Onlygmail.sendis merely sensitive. There is no Google OAuth path to reply detection that avoids the restricted tier. - Incumbents use both. Smartlead's customer-owned OAuth setup requests
gmail.send,gmail.readonlyandhttps://mail.google.com/on the Gmail API (Smartlead help centre). Instantly's Google OAuth requires the Workspace admin to add its client ID as a Trusted app for all users (Instantly help centre). Both also accept SMTP/IMAP with app passwords. - Microsoft Graph
sendMailreturns 202 Accepted, which means accepted, not delivered, and saves to Sent Items by default. SMTP via OAuth needsSMTP.Send; IMAP needsIMAP.AccessAsUser.All(Microsoft docs). - Provider ceilings are generous for cold email. Google Workspace allows 2,000 messages a day per user, 500 on trial accounts; Exchange Online allows 10,000 recipients a day and 30 messages a minute (as of October 2026). Cold senders run at 10–30 a day per inbox for reputation reasons, not because of these limits — see Volume math: what 10k and 100k emails a day cost and Sending limits and throttling.
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:
- Per-inbox daily cap (e.g. 30), with ramp-up for new inboxes and a reduction when bounces rise.
- Minimum and randomised gap between sends from the same inbox (e.g. 8–15 minutes), so traffic looks human.
- Sending window in the prospect's or the sender's time zone, weekdays only.
- Campaign and workspace caps, plus Inbox rotation across the campaign's inbox pool.
- 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 ondue_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. PostgresSELECT … FOR UPDATE SKIP LOCKEDor 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
| Method | Latency | Scale cost | Caveats |
|---|---|---|---|
| IMAP polling | Poll interval | 100k inboxes at 60 s ≈ 1,667 logins or checks per second | Simple, works with any provider |
| IMAP IDLE | Seconds | One long-lived TLS socket per inbox per folder | Gmail caps at 15 simultaneous IMAP connections per account; sockets die silently |
| Gmail push | Seconds | Pub/Sub + history.list | watch() 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 subscriptions | Seconds | Webhook endpoint + renewals | Message 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:
| Activity | Assumption | Units/day | Share of daily project ceiling (1.728B) |
|---|---|---|---|
Renew watch() daily | 100k × 100 | 10M | 0.6% |
| Process inbound mail | 20 messages/inbox/day × (2 + 20) | 44M | 2.5% |
| Send campaign mail | 30/inbox/day × 100 | 300M | 17% |
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
| Type | Signals | Action |
|---|---|---|
| Hard bounce | multipart/report; report-type=delivery-status per RFC 3464; Status: 5.x.x, Action: failed | Stop lead, suppress address, count against inbox health |
| Soft bounce | 4.x.x, mailbox full, greylisting | Retry later, cap retries |
| Auto-reply / OOO | Auto-Submitted: auto-replied (RFC 3834), X-Autoreply, Precedence: auto_reply, return date in body | Pause and resume after the date |
| Human reply | Everything else | Stop 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.comto 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:
- Schedules low volumes of inbox-to-inbox mail between pool members, matched across providers (Google to Microsoft and back) — see ESP matching.
- On the receiving side, uses the reading connector to find warmup messages, move them out of spam, mark them important, open them and reply.
- 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).
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.
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-IDfirst 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).