CD2 · transactional email

Technology teams now ship for dozens of clients.
Transactional email was built when teams shipped for one.

CD2 is the transactional email backbone for multi-client teams — send, receive, and deliver on every client domain from one typed SDK, with tenant isolation in the schema, not in the middleware.

Multi-tenant by schema · Send + receive per domain · Typed SDK

send.ts
import { EmailClient } from "@cdv2/email"

const client = new EmailClient({
  apiKey: "cd2_live_a7…",
});

const { data, error } = await client.send({
  from: "Client A <hello@client-a.com>",
  to  : "user@example.com",
  subject: "Welcome",
  html: "<p>Hello world</p>",
});
Email recorddelivered

to

user@example.com

subject

Welcome

from

hello@client-a.com

Timeline

T+0.0ssent
T+2.1sdelivered

Act 1 · Problem

The shift is already here.

The rules of transactional email changed. Tools built for one brand didn't.

In February 2024, Google and Yahoo rewrote the rules of bulk email — DMARC alignment, one-click unsubscribe, and tight spam-rate ceilings became table stakes for any sender reaching their inboxes. That standard lands on every brand a multi-client team sends for, not just one. Meanwhile agencies now operate across more distinct sending domains per account than they did five years ago, and inbound — replies, legal@, support@, unsubscribes — is increasingly a compliance surface, not an afterthought. The infrastructure assumption every incumbent made — one account, one brand, one sending identity— no longer matches how the work gets shipped.

DMARC

required for bulk senders to Gmail and Yahoo since February 2024.

Google Postmaster Tools guidelines; Yahoo Sender Hub, 2024.

Pending citation · 02

Growth in number of sending domains per agency account over the last 3–5 years.

Source pending · placeholder

Pending citation · 03

Inbound email compliance burden in regulated verticals driving second-vendor costs.

Source pending · placeholder

What breaks when the tool assumes one sender.

Every team operating at agency scale ends up rebuilding the same missing layer.

The failures show up fast and compound quietly. An account holding twenty clients fragments into twenty dashboards, twenty billing lines, and twenty sets of DKIM records to chase every time a record drifts. A credential meant for one client project is technically capable of sending as any sibling client — a blast radius nobody documented and nobody can close. Inbound gets bolted on as a second vendor or a one-off Lambda, which means replies, legal notices, and unsubscribes land in a place no one is accountable for. When one client's deliverability cracks, you can see it in the aggregate but not per-domain, so you find out from the client, not from the data. None of these are incidents. They're the steady cost of running the wrong shape.

Act 2 · Product

Meet CD2.

CD2 is a transactional email platform for technology teams that ship for many clients — built multi-tenant from the schema up, with send and receive on every client domain and a typed SDK that returns { data, error } instead of throwing. It's the layer most agencies eventually build themselves, shipped as a product.

How it's built.

Tenant isolation is in the schema, not in a middleware you hope nobody forgets to apply.

Every email, every API key, every domain, every deliverability event in CD2 is partitioned by a team_id column at the data layer. There is no cross-team read path to turn off, because there was never one to begin with. An API key is, by default, scoped to the team that issued it — and can additionally be bound to a specific subset of that team's domains, so a credential you hand to a single client project is structurally incapable of sending as a sibling client. The scope check runs at the API boundary on every request, before a send is ever accepted. This is not a middleware convention that a future refactor could quietly undo. It's a schema fact.

Schema

partitioned by team_id

TenantTeamisolation unitDomainsclient-a.comsendreceiveAPI keyscd2_live_a7…scope: client-a.comOutboundemailssent · deliveredInboundemailsreplies · legal@ · unsubscribeForwarding rulesexact · local · wildcard
send.ts · runasset · placeholder pending

Scoped domain · 200

region · ca-central-1

// from: "hello@client-a.com"   // key scope ok
{
  data: { id: "em_01HT9…" },
  error: null,
}
sentdeliveredT+2.1s

Pipeline: key.decrypt → team.resolve → scope.check → SES(ca-central-1) → SNS → SQS → deliverability λ

Sibling domain · 403

structurally rejected

// from: "hello@client-b.com"   // out of scope
{
  data: null,
  error: {
    code: "domain_not_in_key_scope",
    status: 403,
  },
}

Stopped at: scope.check — no send dispatched, no record written.

Demonstration

From client.send(…) to a delivered event, on a key that can't cross tenants.

One import, one typed call, one discriminated return. The scope check runs at the API boundary — a sibling-domain send fails structurally, not by convention.

How CD2 is different.

The comparison most multi-client teams are already making silently — named.

 SendGridPostmarkResendCD2
Multi-tenant modelOne account, one brandOne account, one brandOne account, one brandTeam → Domains by design
API keys scoped to a specific client domainWorkaroundFirst-class
Send + receive on every domain in one productPartialYes
Inbound forwarding (exact / local / wildcard)Separate productBuilt in
Domain onboardingDashboard + DNS chaseDashboard + DNS chaseDashboard + DNS chaseOne infra-as-code commit
SDK contractThrows on errorThrows on errorThrows on error (partial types){ data, error }, typed

Every other transactional email tool in this list was designed around a single product sending from a single brand. The multi-client workflows on top of them are workarounds — more accounts, more billing lines, more DNS chases, more credentials you hope nobody leaks sideways. CD2 is the opposite shape: one platform, many isolated client workspaces, designed from the schema up for the work agencies actually do.

See a scoped key, a typed send, and a live deliverability event on your own stack.

Act 3 · Result

What you get instead.

Three things that change structurally, not by convention.

Outcome 01

One account, every client. No blast radius.

Every client lives in its own tenant. API keys can only send as the domain they were scoped to &mdash; structurally, not by policy. A leaked key can't reach a sibling client because the schema won't allow it.

Outcome 02

Send and receive in one product, on every domain.

Inbound &mdash; replies, unsubscribes, legal@ addresses &mdash; routes through the same system as outbound, with forwarding rules at the domain level. No second vendor, no one-off Lambda, no inbox you hope someone is watching.

Outcome 03

Deliverability visible per domain, not in aggregate.

Because every email record carries a domain and a team_id, you see delivery rates, bounces, and complaints per-client &mdash; so you find out before the client does, and you know exactly where to look.

See it against your stack, in 20 minutes.

A 20-minute walkthrough: a scoped key, a typed send, a live deliverability event, and a sibling-domain 403. Then we talk about what onboarding one of your client domains would look like — no slide deck, no follow-up sequence.

© 2026 The Adpharm. All rights reserved.