Get Started
Quickstart
Send your first email in under five minutes.
This guide walks you from a fresh account to a delivered email in four steps. You'll need a verified domain on AWS SES (or a subdomain you can add DNS records for).
Heads up
If you haven't connected AWS yet, read the AWS SES Setup guide first - it takes about 15 minutes.
1. Create an API key
In the dashboard, go to Settings → API Keys and click Create key. Pick a permission level:
Full Access- every endpoint (read, send, manage)Sending Only- restricted toPOST /v1/emails
Your key is shown once, prefixed with efsa_. Copy it into your secrets manager immediately.
2. Verify your domain
Add a domain under Domains → Add domain. You'll receive three DNS records to add to your registrar:
TYPE NAME VALUE
CNAME abcdef._domainkey.yourdomain.com abcdef.dkim.amazonses.com
CNAME ghijkl._domainkey.yourdomain.com ghijkl.dkim.amazonses.com
CNAME mnopqr._domainkey.yourdomain.com mnopqr.dkim.amazonses.comClick Verify. DNS propagation is usually instant on Cloudflare, up to a few hours elsewhere.
3. Send your first email
Fire a request to the send endpoint:
curl -X POST https://api.yourdomain.com/v1/emails \
-H "Authorization: Bearer efsa_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"from": "hello@yourdomain.com",
"to": "ada@lovelace.io",
"subject": "You own this now.",
"html": "<p>Welcome aboard.</p>"
}'You'll get back an email ID. The status transitions through queued → sent → delivered as SNS events arrive.
4. Handle webhooks
Subscribe to delivery events under Settings → Webhooks. Webhooks fire for email.delivered, email.bounced, and email.complained. Each request is signed with HMAC-SHA256 over ${timestamp}.${body}:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyWebhook(
rawBody: string,
headerSignature: string | null, // X-Webhook-Signature, e.g. "v1=abc..."
headerTimestamp: string | null, // X-Webhook-Timestamp
secret: string,
) {
if (!headerSignature || !headerTimestamp) return false;
const signed = `${headerTimestamp}.${rawBody}`;
const expected = createHmac("sha256", secret).update(signed).digest("hex");
const received = headerSignature.replace(/^v1=/, "");
return timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(received, "hex"),
);
}That's it, you're wired up. Next, explore the full API: