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>",
  text: "Hello world",
});
Email record delivered

to

user@example.com

subject

Welcome

from

hello@client-a.com

Timeline

T+0.0s sent
T+2.1s delivered

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

Tenant Team isolation unit Domains client-a.com send receive API keys cd2_live_a7… scope: client-a.com Outbound emails sent · delivered Inbound emails replies · legal@ · unsubscribe Forwarding rules exact · local · wildcard
send.ts · run asset · placeholder pending

Scoped domain · 200

region · ca-central-1

// from: "hello@client-a.com"   // key scope ok
{
  data: { id: "em_01HT9…" },
  error: null,
}
sent delivered T+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.

  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.