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 has the lowest per-message list rate 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 single-key breadth 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 sandbox on the AWS side 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 undercuts anything that wraps it, us included, 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 there only if that AWS familiarity is already in the room. 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
Infrai’s own send costs $0.00046 per recipient on POST /v1/email/send — $0.46 per 1,000 — verified 2026-07-27. Domain verification, suppression checks, message reads and event history are free and rate-limited rather than metered, so the surrounding hygiene work never shows up on the bill. SES’s list rate is lower than that, and MailerSend’s free tier beats both until you outgrow it. At the volume a young product actually sends, the spread across the three is small enough that it shouldn’t decide anything.
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 the other two both beat. Custom sending domains are a Pro-plan capability, declared rather than stumbled into: GET /v1/discovery reports minimum_tier: "pro" for the email.domain.verify capability, so a standard account gets a 402 with a reason your setup script can branch on before it touches DNS. 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 the processing region isn’t selectable the way an AWS one is.
Buy MailerSend if a marketing colleague needs to edit the template without you. Buy SES if unit price is the whole decision and the AWS plumbing is already familiar. Postmark deserves the 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 retry needs a queue, and finance asks which tenant spent what. Those are calls on the same key and lines on the same invoice — no second account, no second onboarding, nothing new to reconcile.