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
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>",
});to
user@example.com
subject
Welcome
from
hello@client-a.com
Timeline
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
Scoped domain · 200
region · ca-central-1
// from: "hello@client-a.com" // key scope ok
{
data: { id: "em_01HT9…" },
error: null,
}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.
| SendGrid | Postmark | Resend | CD2 | |
|---|---|---|---|---|
| Multi-tenant model | One account, one brand | One account, one brand | One account, one brand | Team → Domains by design |
| API keys scoped to a specific client domain | Workaround | – | – | First-class |
| Send + receive on every domain in one product | Partial | – | – | Yes |
| Inbound forwarding (exact / local / wildcard) | Separate product | – | – | Built in |
| Domain onboarding | Dashboard + DNS chase | Dashboard + DNS chase | Dashboard + DNS chase | One infra-as-code commit |
| SDK contract | Throws on error | Throws on error | Throws 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 — 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 — replies, unsubscribes, legal@ addresses — 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 — 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.