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

StepMailerSendAmazon SESInfrai
Account usable for real sendsImmediately, on the free tierSandbox first: verified recipients only, 200 messages per 24h, then a production-access requestImmediately; $2 credit on signup
Verify your sending domainConsole, publish DNSConsole or CLI, publish DNSPOST /v1/email/domain/verify returns the records
SendREST API or SMTPAPI, SMTP or CLIPOST /v1/email/send
Bounce and complaint feedbackBuilt in, plus webhooksPublish to SNS, then write a consumerAutomatic; readable via events and the suppression list
Suppression listManaged for youAccount-level list you configureManaged; GET /v1/email/suppression/list
TemplatesDrag-and-drop builderNone — bring your own rendererStored HTML with named variables
Everything else your app needsA second vendorOther AWS services, other IAMSame 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.

References

Browse more email developer guides