Skip to main content

Split transactional and marketing email into separate streams

A password reset and a Friday promotion have nothing in common except your sending reputation when they leave through one undifferentiated queue. A complaint spike on the promotion then lands your resets in the spam folder, and the resets are exactly the mail you cannot afford to lose. Email streams fix this by partitioning your org’s sending into named streams. Each stream carries its own reputation class (transactional, marketing, or custom), its own throttle ceilings, and its own roster of senders, so one kind of traffic cannot consume or damage another. If you send both classes from the same org, this split is the single biggest deliverability move available to a new sender: do it before warm-up ramps, before volume, before anything else. You will:
  1. Understand what a stream isolates
  2. Create the transactional and marketing streams
  3. Set the default stream
  4. Bind senders to a stream and verify sends
  5. Tune per-stream throttles
  6. Read per-stream delivery and suppression health
  7. Migrate existing senders without a big bang
  8. Know when streams are enough and when to add a dedicated IP

1. What a stream isolates

Every stream you create gives you three separations:
  • Reputation surface. Mailbox providers grade the traffic they see. Bulk marketing mail legitimately runs at higher complaint rates than transactional mail; on a shared identity, those complaints are counted against your receipts too. On separate streams, each class is graded on its own behaviour.
  • Throttle ceilings. Each stream carries its own maxPerMinute and dailyQuota, so the blast class’s pacing is set and read independently of the transactional class’s configuration.
  • Sender roster. A sender belongs to exactly one stream, so what each class sends, and how it performs, is readable per stream instead of blended into one org-wide average.
The kind field is a closed set: transactional, marketing, or custom. Use custom for a sub-brand or a one-off broadcast class that deserves its own reputation surface.

2. Create the transactional and marketing streams

Create both streams with POST /email/streams. Only name is required (1 to 80 characters); kind defaults to transactional when omitted.
The first stream you create becomes the org default automatically. The response is a 201 carrying the full stream row:
Now the marketing stream, with its own throttle ceilings set at creation:
throttle.maxPerMinute and throttle.dailyQuota are positive integers, or null for no per-stream cap (the stream then inherits the org-level send policy). Save both estream_... ids; every step below references them. Two guardrails to know: stream names are unique per org (case-insensitive), so a duplicate name returns 409 and you should edit the existing stream instead; and GET /email/streams lists every stream with its current throttle and default flag at any time.

3. Set the default stream

The org always has exactly one default stream; the platform maintains that invariant on every write. The default is where any send resolves when its sender carries no explicit stream assignment, so make the transactional stream the default: untagged traffic should always take the safe path.
The response returns the promoted stream with isDefault: true and the previous default demoted. Calling set-default on the current default is a no-op, so it is safe to run in automation. Resolution is deterministic: a send takes the stream explicitly assigned to its sender, otherwise the default stream, and an org with no streams configured sends exactly as it did before this feature. You can also promote at create or update time with "isDefault": true.

4. Bind senders to a stream and verify sends

Streams carry no mail by themselves; senders do. Register a sender against a stream with streamId at creation, or re-assign an existing sender with a PUT:
Assigning a streamId that does not exist is rejected with 400 before anything is persisted, so a typo can never leave a sender pointing at a ghost stream. Send "streamId": null to clear an assignment; the sender keeps sending and falls back to the default stream. Templates do not carry a stream; the send’s sender does. Render a template as usual and dispatch it from the from address of a sender bound to the right stream. A send returns the same 202 envelope regardless of stream, with data.succeeded rows and meta.request_id:
To verify which stream a send travelled on, read the sender back with GET /email/senders: each sender row carries its streamId, and the from address on the send resolves to that sender. Correlate the meta.request_id with your webhook events to follow the message through delivery on that stream’s traffic.

5. Tune per-stream throttles

Throttles are edited in place with PUT /email/streams/:streamId:
The update semantics are deliberate:
  • Omitting throttle leaves the existing ceilings unchanged.
  • Sending an explicit empty object { "throttle": {} } clears both ceilings back to null (no per-stream cap).
  • Each ceiling is independent: set maxPerMinute alone and dailyQuota stays as it was.
Size the marketing stream’s ceilings to the blast you actually plan, and keep the transactional stream uncapped or capped far above your peak receipt volume. The protection a blast gets from the split is the partition itself: the campaign’s senders live on the marketing stream, so its pacing and complaint exposure stay with that stream and never touch the reputation of the senders your receipts use. The ceilings you store are each stream’s own pacing policy, and the org-wide send pool still applies to every send, so a pool-level 429 is not a stream-throttle rejection. If the marketing ceilings are too tight for a planned send, raise them here rather than moving marketing mail onto the transactional stream.

6. Read per-stream delivery and suppression health

Because a sender belongs to exactly one stream, per-stream health is the roll-up of that stream’s senders. Two surfaces cover it:
  • Webhooks. Subscribe message.delivered, message.failed, email.opened, and email.clicked as in Send your first transactional email, and group events by the sending address. Each address maps to one sender, each sender to one stream, so delivery, bounce, and complaint rates split cleanly per stream.
  • The dashboard. Channels → Email shows delivery and engagement for your sends, and the Sent Emails log carries the sending address on every row, so you can read each stream’s traffic the same way.
Watch the complaint and hard-bounce rate per stream after every large blast; a rising complaint rate on the marketing stream is now a marketing-stream problem, contained where it belongs. Suppressions are org-wide on purpose: hard bounces, exhausted soft bounces, complaints, and unsubscribes are held out of all future sends on every stream, with the quota slot refunded. A recipient who hard-bounced a campaign should not receive a retried password reset either; streams separate reputation exposure, not the suppression list. Manage it under Channels → Email → Suppressions.

7. Migrate existing senders without a big bang

If you already send both classes through one undifferentiated setup, move in steps rather than flipping everything at once:
  1. Audit. List your senders (GET /email/senders) and label each one by what it actually sends: transactional, marketing, or mixed.
  2. Create the streams and set the default. Follow steps 2 and 3, with the transactional stream as default so any sender you miss keeps resolving to the safe path.
  3. Move clean senders first. Senders that are purely transactional or purely marketing get their streamId set immediately with PUT.
  4. Split mixed senders by percentage. For a sender that sends both, register a second sender address for one class (for example news@yourdomain.com for marketing), point a slice of that traffic at it, and raise the share over one to two weeks while you watch both streams’ delivery and complaint deltas.
  5. Compare deltas. Delivery rate and complaint rate per stream, before and after each move. The transactional stream’s numbers should stay flat or improve as marketing volume leaves it.
Deleting a stream later never breaks its senders: they read as unassigned and keep sending through the default stream, and the most recently created remaining stream is promoted if you delete the default.

8. When to add a dedicated IP

Streams partition reputation within your sending identity; a dedicated IP gives you an IP reputation that is entirely your own. Streams alone are enough while your volume sits comfortably in the shared pool and your two classes behave differently enough that separating them removes the cross-contamination. Add a dedicated IP when your sustained volume is high enough to sustain its own warm-up and reputation, or when a class’s traffic pattern (very large periodic blasts) would swing a shared IP’s standing. The warm-up ramp and volume posture for that step are covered in the Email channel lifecycle guide. The two controls compose: dedicated IP for isolation at the IP layer, streams for isolation between traffic classes on top of it.