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>",
text: "Hello world",
}); 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. Everything CD2 does — the SDK,
the dashboard, the deliverability pipeline — rests on it.
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 can now do.
The capabilities a multi-client team ships with on day one of using CD2.
Outcome 01
Onboard a client domain in one commit.
Add the domain in the infrastructure-as-code manifest, apply, and DKIM, SPF, receipt rules, and the dashboard record are provisioned together. No three-UI chase, no support ticket, no reviewer asking “where did this domain come from.”
Outcome 02
Hand each client project a credential that can’t go rogue.
Issue a CD2 API key scoped to a specific client domain and the credential is structurally incapable of sending as a sibling client — regardless of who holds it, leaks it, or hardcodes it. The blast radius of a lost key is bounded by schema.
Outcome 03
Act on deliverability in real time, per client.
Every bounce, complaint, and delay lands on the email record as it arrives from the infrastructure, dimensioned by domain. You can suppress, alert, and triage per client — without buying a second analytics tool or parsing SNS yourself.
Already running in production.
CD2 is not a demo. It’s a live platform carrying agency traffic today.
Built on
- AWS SES
- AWS Lambda
- AWS SQS
- AWS SNS
- Neon
- Vercel
- Terraform
CD2 is in production across multiple live agency domains in
regulated and consumer-facing verticals. It’s deployed in
ca-central-1
on AWS SES, Lambda, SQS, SNS, and Neon Postgres, with every piece
of infrastructure managed in Terraform. Inbound and deliverability
event processing run at-least-once with dead-letter queues and an
hourly automated redrive — messages aren’t silently
dropped. API keys are AES-256-CBC encrypted at rest and compared
in constant time. The access path from the operator web app to
the shared worker database runs through a single JWT-signed SDK,
so there is exactly one auditable surface into the email data.
See it against your stack, in 20 minutes.
Bring one client domain and one sending use case. We’ll walk through a scoped key, a typed send, send + receive on the domain, and a live deliverability event landing on the email record — on your own code, with your own data flowing. No account setup ahead of time, no slide deck.