v0.1 Send, track and prove delivery

Transactional email that tells you what happened.

One call to send. A durable queue, DKIM-signed delivery through Amazon SES, and an event timeline for every message, in your own AWS account and your own database.

No vendor lock-in · your IPs, your data, your bill from AWS

curl https://api.relykit.com/emails \
  -H "Authorization: Bearer rlk_live_..." \
  -H "Idempotency-Key: order-6be1f2-receipt" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <receipts@acme.com>",
    "to": "kai@example.com",
    "subject": "Your receipt",
    "html": "<p>Thanks for your order.</p>",
    "tags": { "order_id": "6be1f2" }
  }'

then

Your receipt → kai@example.com delivered
  1. email.sent 0.4s
  2. email.delivered 1.9s
  3. email.opened 2m 11s
  4. email.clicked 2m 24s

What you get

Not a black box with a status code.

Every message keeps its own record: what you sent, what Amazon did with it, what the receiving server said back, and everything that happened afterwards.

Sent · 24h 1,522 limit 50,000
Delivered · 24h 1,512 reached the mailbox
Problems · 24h 10 bounced, rejected or failed
In flight 12 queued or sending

What happened to your mail

last 14 days, UTC
Delivered Bounced Complained Never sent
0 500 1,000 1,500 2,000 4 Sep 7 Sep 10 Sep 13 Sep 16 Sep
The numbers
DayDeliveredBouncedComplainedNever sentTotal
17 Sep 1,512901 1,522
16 Sep 1,4881312 1,504
15 Sep 1,4211001 1,432
14 Sep 771710 779
13 Sep 806800 814
12 Sep 1,3383122 1,373
11 Sep 1,29096511 1,402
10 Sep 1,3675834 1,432
9 Sep 1,4021520 1,419
8 Sep 1,3101201 1,323
7 Sep 688913 701
6 Sep 742600 748
5 Sep 1,2441101 1,256
4 Sep 1,1801412 1,197

One message, end to end

5 events
  1. Queued email.queued
    kai@example.com · 14:02:11
  2. Sent email.sent
    handed to Amazon SES · 14:02:12
  3. Delivered email.delivered
    mx.example.com accepted it · 14:02:14
  4. Opened email.opened
    iPhone · Apple Mail · 14:39:50
  5. Clicked email.clicked
    https://acme.com/receipt/6be1f2 · 14:40:06
The real pages, with invented numbers — 16,902 messages over a fortnight, including the bad Thursday. Every message carries a timeline like that one, per recipient, whether or not anybody subscribes to the webhook.
10types
events on every message timeline, per recipient
6records
DKIM, SPF and DMARC, each checked on its own
11hours
of webhook retries before an endpoint is given up on
1dependency
Postgres. No Redis, no broker, no queue service

The shape of it

The delivery half runs in your account, not ours.

Which is why the bill splits in two, and why the second half is Amazon's price rather than somebody's margin on it.

  1. Your application

    One HTTPS call, or SMTP on port 587 if that is what you already speak.

  2. RelyKit

    Idempotency, the queue, suppression, DKIM signing, tracking and the timeline.

    billed by RelyKit

  3. Amazon SES in your AWS account

    The reputation, the IP pools and the actual handoff to the receiving server.

    billed by Amazon, at cost

  4. The inbox

    Or a bounce, a complaint, or a deferral — each one a fact you get back.

Everything that happens comes back. SES publishes to SNS, RelyKit drains it from SQS, and the result reaches your timeline and your signed webhook — delivered, bounced, complained, opened, clicked.

Two bills, not one marked up. Nobody stands between you and Amazon's price, which is most of what a month costs once you are sending in volume.

On the wire

Delivery is a conversation. You get the transcript.

Behind every send is an SMTP exchange with the receiving server. Amazon SES reports how that exchange ended, and each outcome becomes an event on the message and, if you want it, a signed POST to your endpoint.

mx.example.com · accepted delivered
EHLO send.acme.com
250-mx.example.com
MAIL FROM:<bounces@send.acme.com>
250 2.1.0 Ok
RCPT TO:<kai@example.com>
250 2.1.5 Ok
DATA … DKIM-signed message … email.sent
250 2.0.0 Ok: queued as 4X7pQ2 email.delivered
Nothing to do.
  • The timeline records it as delivered, with that exact reply beside it.
  • Your endpoint gets email.delivered if it subscribes to it.
  • Opens and clicks follow later, if tracking is on.
mx.example.com · refused bounced
EHLO send.acme.com
250-mx.example.com
MAIL FROM:<bounces@send.acme.com>
250 2.1.0 Ok
RCPT TO:<gone@example.com>
550 5.1.1 User unknown email.bounced
QUIT
221 2.0.0 Bye
Handled without you.
  • The address is suppressed, so nothing is sent to it again.
  • Your endpoint gets email.bounced with the diagnostic code.
  • The bounce counts toward the rate that pauses the account.

How it works

Verify a domain, then send.

Adding a domain creates an Amazon SES identity with its own DKIM keys and a custom MAIL FROM subdomain, so SPF aligns and DMARC passes. You publish six records; we check each one and say exactly which is wrong.

Add your domain

One call to POST /domains returns the records to publish, already filled in with your keys and region.

Publish and verify

Records are re-checked against public resolvers every five minutes, and the moment DNS and SES agree the domain goes live.

Send and watch

Messages queue in Postgres, send through SES, and report back every delivery, bounce, complaint, open and click.

What your DNS ends up holding

TypeHostValuePurpose
CNAMEk1._domainkey.acme.comk1.dkim.amazonses.comDKIM signature
CNAMEk2._domainkey.acme.comk2.dkim.amazonses.comDKIM rotation
CNAMEk3._domainkey.acme.comk3.dkim.amazonses.comDKIM rotation
MXsend.acme.com10 feedback-smtp.eu-west-1.amazonses.comBounce return path
TXTsend.acme.comv=spf1 include:amazonses.com ~allSPF alignment
TXT_dmarc.acme.comv=DMARC1; p=none; rua=mailto:…DMARC reporting

Built in

The parts you would otherwise write twice.

Event timelines

Sent, delivered, delayed, bounced, complained, rejected, opened, clicked, failed and suppressed, recorded per recipient, with the provider’s own response attached.

GET /emails/:id/events

Signed webhooks

HMAC-SHA256 headers with a five minute replay window, retries at 1m, 5m, 30m, 2h and 8h, and an endpoint that disables itself and emails you when it keeps failing.

webhook-signature: v1,…

Idempotency keys

Retry a send after a timeout and get the original message back instead of a duplicate in someone’s inbox.

Idempotency-Key: order-6be1f2

A durable queue

Messages land in Postgres before anything tries to deliver them. Workers claim rows with locks, back off on throttling, and pick up where a crash left off.

FOR UPDATE SKIP LOCKED

Open and click tracking

On per account, through a separate SES configuration set, so untracked mail carries no pixel and no rewritten links.

email.opened · email.clicked

Deliverability

The guardrails run whether or not anyone is watching.

On shared infrastructure one bad list ruins everyone's inbox placement. Amazon suspends senders above 10% bounces or 0.5% complaints. These limits are half of that, so sending stops here first.

  • Suppressed addresses are dropped before a message is queued, and the message records which and why.
  • One-click unsubscribe headers, as Gmail and Yahoo require of bulk senders.
  • New accounts send under a low cap until a human approves them.
  • Rates over the line pause the account and email its owner.

Account reputation

healthy
Sent · 24h 18,402
Hard bounces 0.31% pauses at 5.00%
Complaints 0.02% pauses at 0.25%
Recomputed continuously from the events above, and only judged once there is enough volume for a ratio to mean something.

Webhooks

Find out the moment a message fails.

Every event is delivered as a signed JSON POST carrying the message it belongs to, so your handler never has to call back for context.

  • Filter per endpoint, or take everything.
  • Every attempt recorded with its status code and response.
  • Send a test event from the dashboard before you go live.
Webhook reference
POST /hooks/relay
{
  "id": "5f2c1b7e-9d34-4a11-9d1e-5b2a0c7e4f31",
  "type": "email.bounced",
  "created_at": "2026-09-16T09:41:12.004Z",
  "data": {
    "email_id": "1f0a9c22-0e51-4d0c-8c5a-7f9b2a1d3e44",
    "recipient": "kai@example.com",
    "from": "Acme <receipts@acme.com>",
    "subject": "Your receipt",
    "tags": { "order_id": "6be1f2" },
    "bounce_type": "hard",
    "sub_type": "General",
    "diagnostic_code": "smtp; 550 5.1.1 user unknown"
  }
}
verify it
import { verifyWebhook } from 'relykit';

app.post('/hooks/relay', express.raw({ type: 'application/json' }), (req, res) => {
  const ok = verifyWebhook({
    secret: process.env.RELYKIT_WEBHOOK_SECRET,
    headers: req.headers,
    body: req.body.toString('utf8'),
  });
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body);
  if (event.type === 'email.bounced') suspend(event.data.recipient);
  res.sendStatus(200);
});

SDK

A typed client with no dependencies.

Retries on 429 and 5xx, honours Retry-After, and turns every API error into one typed exception.

install
npm install relykit
handle failure once
try {
  await relykit.emails.send(payload, { idempotencyKey: order.id });
} catch (error) {
  // 429 and 5xx were already retried, honouring Retry-After.
  if (error.code === 'daily_limit_exceeded') return sendTomorrow(payload);
  if (error.code === 'domain_not_verified') return alertOps(error.message);
  throw error;
}

Pricing

You pay Amazon for delivery. RelyKit is the part around it.

Sending runs in your own AWS account, so the delivery cost lands on your AWS bill at Amazon's rate and nobody marks it up. These are the plans for the part around it: the queue, the tracking, the signing and the suppression.

Free

Freeno card, no expiry

Enough to build against and to run something small.

  • 3,000 messages a month
  • 300 a day
  • One sending domain
  • Nothing charged beyond it
Start here

Starter

$19a month

For a product with real users.

  • 50,000 messages a month
  • 5,000 a day
  • 3 sending domains
  • Then $0.80 per thousand
Start here

Growth

$99a month

For a business whose email volume is part of the business.

  • 500,000 messages a month
  • 50,000 a day
  • 25 sending domains
  • Then $0.50 per thousand
Start here

Scale

Talk to usvolume by agreement

Negotiated volume, with the limits lifted.

  • As much as you need
  • 1,000,000 a day
  • 1,000 sending domains
  • Limits lifted to suit
Read the docs first

What a month actually costs

Including the bill you pay Amazon, which most comparisons leave out. Drag it to your volume. Where someone else is cheaper, it says so.

250,000

RelyKit Growth + Amazon

$124

$99 to us, $25 to Amazon

Resend Pro

$200

delivery included, at their rate

$76

a month cheaper here

Every volume, and where the lines cross

Where Resend has more than one plan that covers a volume, the table quotes their cheapest, not the one that flatters us. SendGrid is not quoted at all: moved to Twilio during the merger; we could not read a current price, so we are not quoting one. Their prices are on their own page.

A month Plan To us To Amazon Total Resend Difference
3,000 Free $0 $0.30 $0.30 $0 $0.30 cheaper there
10,000 Starter $19 $1 $20 $20 the same
50,000 Starter $19 $5 $24 $20 $4 cheaper there
100,000 Starter $59 $10 $69 $65 $4 cheaper there
250,000 Growth $99 $25 $124 $200 $76 cheaper here
500,000 Growth $99 $50 $149 $425 $276 cheaper here
1,000,000 Growth $349 $100 $449 $875 $426 cheaper here
2,500,000 By agreement $250 $2,225
5,000,000 By agreement $500 $4,475

Resend prices read from their pricing page on 2026-09-17. Amazon SES is $0.10 per thousand.

Nothing is billed today. Usage has been metered from the first message, so when there is a price you will be able to see exactly what it would have cost.

Your first email in about ten minutes.

Sign in with an email address, add a domain, publish six DNS records, and send. The dashboard shows every message and everything that happened to it.