email·digit
For developers

Transactional, typed,
observable.

One REST surface across email, WhatsApp, and SMS. Idempotency keys, signed webhooks, trace IDs in every send, edge-runtime compatible when SDKs land. Today, the API speaks fluent curl in every language you ship with.

One endpoint · seven languages

Send your first email
in 30 seconds.

The whole API is REST + JSON. Pick the language you ship with — the request shape is the same across all of them. Native SDKs (npm · pip · go get · gem · composer) are Q3 2026.

POST /api/campaigns/send
# 1. Send a transactional email — one POST
curl https://api.emaildigit.com/api/campaigns/send \
  -H "Authorization: Bearer $ED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "amelia@stripe.com",
    "subject": "Welcome to Acme",
    "html": "<p>Hi Amelia, your account is ready.</p>"
  }'

# →
{
  "send_id": "snd_8f3k2p4xN",
  "status": "queued",
  "trace_id": "tx_aWelcomeEmail9k2"
}
What you get

The boring infrastructure
you would have built anyway.

Idempotency, retries, signing, traces. Same patterns as Stripe or Plaid — minus the email-specific cruft you'd otherwise wire up by hand.

API tokens · ed_live_…

Live

Per-workspace bearer credentials. Generate from the dashboard, copy once, store in your secrets manager. Tokens self-identify with the ed_live_ prefix so GitHub secret scanning catches accidental commits.

HMAC-SHA256 webhooks

Live

Sign payloads with sha256({ts}.body). Receivers verify in 10 lines (snippet above). Replay defense via 5-minute timestamp window.

Exponential-backoff retry

Live

1m → 5m → 30m → 2h → 12h → dead. Auto-pause after 25 consecutive failures so a permanently-broken endpoint stops eating queue capacity.

Audit log · 7 year retention

Live

Every state change emits an append-only audit entry. Surfaces in /dashboard/audit and via API. SOC 2 evidence built-in.

Idempotency keys

Q3 2026

Pass Idempotency-Key: <your-uuid> on any POST. Same key inside 24h returns the prior response — no duplicate sends, no charge.

Trace IDs across the funnel

Q4 2026

Each ed.send() returns a trace_id that follows the message through delivery → opens → clicks → replies. Pipe it into Datadog / Honeycomb / your OTEL stack.

Receiving events

Webhooks you can trust
in 10 lines.

Every event is HMAC-SHA256-signed. Verify the signature, drop unsigned requests, dispatch on event type. Two of the most-deployed examples below — same pattern across every other language.

POST /webhooks/email-digit
// Verify the X-Email-Digit-Signature header on inbound deliveries
import crypto from "crypto";
import express from "express";

const SECRET = process.env.ED_WEBHOOK_SECRET;

function verify(rawBody, header) {
  const [sigPart, tsPart] = header.split(",");
  const sig = sigPart.slice("sha256=".length);
  const ts = tsPart.slice("t=".length);
  // Reject anything older than 5 minutes (replay defense)
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${ts}.`)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

// IMPORTANT: use express.raw — JSON parsers re-serialize and break the MAC
app.post("/webhooks/email-digit",
  express.raw({ type: "application/json" }),
  (req, res) => {
    if (!verify(req.body, req.get("X-Email-Digit-Signature"))) {
      return res.status(401).send("invalid signature");
    }
    const event = JSON.parse(req.body.toString("utf8"));
    // dispatch on event.type
    res.status(200).send("ok");
  });
Event types

Eight events. More on the way.

Subscribe to all of them with `*`, or just the ones you need. Same payload envelope across every type.

reply.receivedA reply lands in the workspace (email or WhatsApp), after classification.
email.sentAn outbound email is accepted by the relay.
email.bouncedAn outbound email fails at the relay (4xx/5xx/timeout).
domain.verifiedA sending domain transitions to `verified` status.
domain.failedA previously-verified domain transitions back to `pending`.
mailbox.connectedA new inbox is connected (Gmail / Outlook / IMAP).
whatsapp.receivedAn inbound WhatsApp message arrives (also fires `reply.received`).
test.pingSent when you click `Send test` in the dashboard. Useful for setup verification.
Local development

Sandbox · staging · production.
Same API surface.

Local
127.0.0.1:8001

Run our backend locally with uv + Postgres. Five-minute setup; full feature parity.

Staging
api-dev.emaildigit.com

Auto-deploys from the dev branch. Connects to a Neon branch DB. Safe to break.

Production
api.emaildigit.com

Auto-deploys from main. Real customer data. The ed_live_ token tells you you're hitting it.

FAQ

Things developers actually ask.

When will there be a real SDK?

Q3 2026 for TypeScript + Python. Go, Ruby, PHP, Rust follow. Until then the REST API is small enough to wrap in a 100-line module per language — and we'll publish those wrappers as community-maintained references along the way.

What's the rate limit?

Per-token: 100 req/s burst, 10 req/s sustained, 100k req/day. Per-workspace: 10× that. Free tier: half those numbers. We return X-RateLimit-Remaining on every response and 429 with Retry-After when you cross it. Email sends count separately — those are governed by your tier's monthly cap.

Can I use this from an edge runtime (Cloudflare Workers / Vercel Edge)?

Yes — the REST API works from anywhere that can make HTTPS requests. Once SDKs ship they'll be edge-compatible (no Node-only deps).

How do I test webhooks locally?

Tunnel to your laptop with ngrok or Cloudflare Tunnel, paste the URL into /dashboard/webhooks, click Send test. You'll get a test.ping event within ~30 seconds. Replay any delivery from the dashboard.

Is the API versioned?

Endpoints live under /api/ today (no version prefix). When we ship v1 we'll keep the current routes alive for a year minimum. Breaking changes are announced in the changelog.

What happens when my token leaks?

Revoke it in /dashboard/api-tokens — takes effect within 30 seconds. Mint a new one. The audit log records every request the leaked token made, so you can scope blast-radius investigations. GitHub's secret scanner detects ed_live_ prefixes and alerts you within minutes if you accidentally push one to a public repo.

For developers

Ship in an afternoon.
Sleep at night.

Request access, mint a token, paste a curl. Most teams have their first send delivered before the kettle boils.