MailerSend vs Amazon SES vs an aggregated API: a beginner's first send
Counted by steps to the first welcome email from your own domain: a drag-and-drop ESP, raw AWS plumbing, or one key that also covers storage, cron and errors.
Judge these three by how many things you have to build before a real signup gets a real welcome email from your own domain. MailerSend gets you there fastest if email is all you want; Amazon SES is the cheapest per message and asks you to assemble the rest; Infrai sits in the middle on both counts and reaches twenty-odd other capabilities on the same key. That last property is the tiebreaker for a backend that will shortly need object storage and a queue anyway.
None of them is wrong. What differs is how much of the surrounding machinery — sandbox approval, bounce plumbing, a suppression store — you own.
Steps to the first welcome email
| Step | MailerSend | Amazon SES | Infrai |
|---|---|---|---|
| Account usable for real sends | Immediately, on the free tier | Sandbox first: verified recipients only, 200 messages per 24h, then a production-access request | Immediately; $2 credit on signup |
| Verify your sending domain | Console, publish DNS | Console or CLI, publish DNS | POST /v1/email/domain/verify returns the records |
| Send | REST API or SMTP | API, SMTP or CLI | POST /v1/email/send |
| Bounce and complaint feedback | Built in, plus webhooks | Publish to SNS, then write a consumer | Automatic; readable via events and the suppression list |
| Suppression list | Managed for you | Account-level list you configure | Managed; GET /v1/email/suppression/list |
| Templates | Drag-and-drop builder | None — bring your own renderer | Stored HTML with named variables |
| Everything else your app needs | A second vendor | Other AWS services, other IAM | Same key, same bill |
The SES sandbox is where beginners lose an afternoon. Until you request production access you can only send to addresses you’ve verified, which means your staging signup flow appears to work and your first real customer gets nothing.
What each one is actually good at
MailerSend suits a small team whose email is mostly designed rather than programmed — a marketing person can edit the welcome template without opening a pull request, and the free tier around 3,000 messages a month covers a young product. Check its current pricing page before you plan around that; free tiers in this market get re-cut every year or so.
SES is the value option and stays the value option. At roughly $0.10 per 1,000 messages outbound it’s cheaper than anything wrapping it, including us — our send is $0.115 per 1,000 — and if you’re already deep in AWS with Lambda, SNS and CloudWatch in your muscle memory, the plumbing tax is close to zero. It’s also the only one of the three that lets you pick a specific AWS region for processing, which matters if your DPA names one.
We’d point a beginner at SES only if that AWS familiarity is already there. Wiring an SNS topic to a Lambda to parse bounce notifications is a couple of days’ work the first time, and it’s work you can’t skip, because unmonitored bounces are how a new domain gets throttled.
Setting the domain up here
export INFRAI_API_KEY="your_infrai_api_key"
curl -sS -X POST "https://api.infrai.cc/v1/email/domain/verify" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"domain":"hello.example.com"}'
Publish the three TXT records it returns, then call the same route again until status reads verified. Sending from a domain that’s still pending_dns gets you EMAIL_INVALID_STATE rather than a silent fallback, which is the behaviour you want in CI.
For contrast, the equivalent on AWS looks like this once your identity is verified and production access has been granted:
aws sesv2 send-email \
--region eu-west-1 \
--from-email-address "hello@hello.example.com" \
--destination "ToAddresses=newuser@example.com" \
--content '{"Simple":{"Subject":{"Data":"Welcome"},"Body":{"Html":{"Data":"<p>Your account is live.</p>"}}}}'
Structurally similar. The difference isn’t this call — it’s the four other resources you need around it.
The welcome send, and the check that should precede it
curl -sS "https://api.infrai.cc/v1/email/suppression/check/newuser@example.com" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"ok": true,
"data": {
"email": "newuser@example.com",
"suppressed": false,
"reason": null,
"added_at": null
}
}
curl -sS -X POST "https://api.infrai.cc/v1/email/send" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"to":"newuser@example.com","from":"hello@hello.example.com","subject":"Welcome to Acme","html":"<p>Your workspace is ready. <a href=\"https://app.example.com/start\">Open it</a>.</p>"}'
{
"ok": true,
"data": {
"message_id": "msg_Zq7yPn4RtL9cKw3sVb",
"from_used": "hello@hello.example.com",
"mode": "live",
"accepted_recipients": ["newuser@example.com"],
"suppressed_recipients": []
}
}
The pre-check is optional — the send route enforces suppression anyway and reports blocked addresses in suppressed_recipients — but doing it first lets your signup flow tell the user something useful instead of pretending mail went out.
The signup handler, end to end
import process from "node:process";
const API = "https://api.infrai.cc";
const KEY = process.env.INFRAI_API_KEY;
if (!KEY) throw new Error("INFRAI_API_KEY is not set");
const auth = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };
async function call(path, init = {}) {
const res = await fetch(API + path, { ...init, headers: auth });
const payload = await res.json().catch(() => ({}));
if (!res.ok || payload.ok === false) {
const err = payload.error ?? { code: `HTTP_${res.status}`, message: res.statusText };
throw new Error(`${path} -> ${err.code}: ${err.message}`);
}
return payload.data;
}
export async function onSignup({ email, workspace }) {
const screen = await call(`/v1/email/suppression/check/${encodeURIComponent(email)}`);
if (screen.suppressed) {
return { emailed: false, reason: screen.reason ?? "suppressed" };
}
const sent = await call("/v1/email/send", {
method: "POST",
body: JSON.stringify({
to: email,
from: "hello@hello.example.com",
subject: `Welcome to ${workspace}`,
html: `<p>Your workspace <strong>${workspace}</strong> is ready.</p>`,
}),
});
return { emailed: sent.accepted_recipients.length > 0, messageId: sent.message_id };
}
const outcome = await onSignup({ email: "newuser@example.com", workspace: "Acme" });
console.log(outcome);
Two calls, no SDK, no queue consumer, and nothing to deploy. Store messageId on the user row — it’s what you’ll need when support asks whether the welcome mail was delivered, and GET /v1/email/get/{id} answers that in one request.
Money, with a vintage on it
Our send costs $0.000115 per recipient, verified 2026-07-26, and the $2 new-account credit covers roughly 17,391 messages. Domain verification, suppression, message reads and event history are free and rate-limited rather than metered. SES is about 13% cheaper per message at list rate, and MailerSend’s free tier beats both until you outgrow it — at 5,000 welcome emails a month you’re comparing $0.58 against $0.50 against nothing, which is not a number anyone should choose a platform on.
Rates here move down, not up, as vendor discounts land. Read the current one:
curl -sS "https://api.infrai.cc/v1/discovery" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
| python3 -c "import json,sys; d=json.load(sys.stdin); print(next(c['billing'] for c in d['capabilities'] if c['id']=='email.send'))"
Drawbacks, stated plainly
There are no outbound webhooks here, so delivery status is something you poll for rather than something that arrives — a real limitation if you need sub-minute reaction to a bounce, and one MailerSend and SES both beat. Custom sending domains sit behind a paid plan; POST /v1/email/domain/verify returns HTTP 402 PRO_REQUIRED on a standard account. Template tooling is HTML plus named variables, with no visual builder, so a non-technical colleague can’t edit the welcome copy without you. And processing region isn’t selectable the way an SES region is.
If email is the only thing you’re buying, take MailerSend for the builder or SES for the price, and both are defensible. Postmark is the third answer worth a look when deliverability support matters more than either.
The argument for the aggregated option is what happens in week three, when the welcome email needs an attached PDF in object storage, the resend needs a queue, and finance asks which tenant spent what. Those are calls on the same key and lines on the same invoice, rather than three more signups.