Choosing an email service for resets and welcomes: six API tests
Reset mail and welcome mail want different things from a provider. Six checks you can run against any API-first vendor in an afternoon, with the Infrai calls that answer them.
There isn’t one best service for both messages, because a password reset and a welcome email fail in opposite directions. A reset is worthless if it lands four minutes late or in Promotions, and it must never carry an unsubscribe footer. A welcome email can wait, should carry your brand, and belongs on a list a recipient can leave. Infrai handles both from one key, but the honest answer to “which service” is a short set of tests you can run against any candidate — including this one — before you commit.
Six of them, all API calls, no SMTP credentials involved. If a vendor makes you answer any of these with a dashboard screenshot, that’s your result.
The six tests
- Can I send my first message without owning a verified domain?
- Is domain verification an API call that returns the DNS records to publish?
- Can I read the suppression list, and check one address, programmatically?
- Is per-message delivery history readable by id, without a webhook receiver?
- Can I keep transactional mail out of marketing streams and unsubscribe headers?
- What does a failure look like — a structured code, or an HTML error page?
Nothing here is about price. Price is the easiest thing to change later; a missing suppression API is a rewrite.
Test 1 and 2: getting a message out, then getting your domain on it
The fastest first send is the one that needs no DNS at all. Omit from and the platform signs with its own domain:
export INFRAI_API_KEY="your_infrai_api_key"
curl -sS -X POST "https://api.infrai.cc/v1/email/send" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"to": "dana@example.com",
"subject": "Reset your Kettle password",
"html": "<p>Use this link within 30 minutes: <a href=\"https://kettle.example/r/6f1c9d2a\">choose a new password</a>. If this wasn'\''t you, ignore this email.</p>"
}'
That answers with mode: "default_vendor" and a from_used on send.infrai.cc. Fine for an internal tool or a beta; not fine for a consumer reset, where an unfamiliar sender looks exactly like the phishing it’s competing with.
Your own domain is POST /v1/email/domain/verify, and the response carries the SPF, DKIM, tracking and DMARC records to publish. Once published, the record shows its verification and reputation state:
curl -sS "https://api.infrai.cc/v1/email/domain/list" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{
"ok": true,
"data": {
"records": [
{
"domain": "mail.kettle.example",
"status": "verified",
"checks": { "spf_dns": "verified", "dkim_dns": "verified", "tracking_cname": "verified", "dmarc_dns": "verified", "mail_loopback": "verified" },
"warm_up_state": "in_progress",
"daily_limit_current": 50000,
"daily_limit_target": 500000
}
]
}
}
Here’s the caveat that belongs in your evaluation, not in a footnote after you’ve migrated: on a standard Infrai account, POST /v1/email/domain/verify and any send carrying a custom from answer HTTP 402 PRO_REQUIRED. The dedicated-domain story is a paid-tier story. If a dedicated sending domain is your day-one requirement and you’d rather not buy a tier for it, Resend or Postmark will get you there faster.
warm_up_state and the growing daily_limit_current are the other half of the same subject. A brand-new domain that suddenly emits 50,000 messages gets throttled by receivers regardless of vendor, so any provider that lets you ramp — and tells you where you are in the ramp — is doing you a favour.
Test 3 and 4: suppression and per-message history
A bounce that isn’t suppressed becomes a second bounce, and enough of those cost you the domain. Both operations are free reads:
curl -sS "https://api.infrai.cc/v1/email/suppression/check/dana@example.com" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
{ "ok": true, "data": { "email": "dana@example.com", "suppressed": false } }
Screening before a reset send is the pattern worth stealing whichever vendor you choose: if the address is suppressed, telling the user “check your inbox” is a lie, and support gets the ticket three days later.
The list read carries the reason and the date, which is what an auditor or a GDPR request actually asks for:
curl -sS "https://api.infrai.cc/v1/email/suppression/list?limit=3" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
Per-message history is GET /v1/email/event/list?message_id=…, newest first, no webhook receiver required. That matters more for resets than people expect — when a user says the mail never arrived, you want a timeline for that one message inside ten seconds, not a dashboard search.
curl -sS "https://api.infrai.cc/v1/email/event/list?message_id=msg_FjDRVM4y1dx7xcElLubSlJMF" \
-H "Authorization: Bearer ${INFRAI_API_KEY}"
There are no webhooks on this surface at all. For a reset flow that’s arguably an advantage (you’re polling one message a human is waiting on); for an analytics pipeline ingesting opens and clicks at volume, it’s a drawback, and a vendor with signed event webhooks is the better buy.
Test 5: keeping the two message types apart
// audit.mjs — Node 22 ESM. Runs the read-only half of the checklist.
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");
async function get(path) {
const res = await fetch(API + path, {
headers: { authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(10_000),
});
const payload = await res.json().catch(() => ({}));
if (!res.ok || payload.ok === false) {
const e = payload.error ?? {};
return { ok: false, code: e.code ?? `HTTP_${res.status}`, message: e.message ?? res.statusText };
}
return { ok: true, data: payload.data };
}
const checks = {
"domains readable": () => get("/v1/email/domain/list"),
"suppression readable": () => get("/v1/email/suppression/list?limit=5"),
"single address check": () => get("/v1/email/suppression/check/dana@example.com"),
"message archive": () => get("/v1/email/list?limit=5"),
};
const report = {};
for (const [label, run] of Object.entries(checks)) {
const out = await run();
report[label] = out.ok ? "pass" : `fail (${out.code})`;
}
console.table(report);
On the send itself, message_class defaults to transactional, and that default is what keeps an unsubscribe link off a password reset — a reset with a “stop receiving these” footer is a support incident waiting to happen. Passing "marketing" answers HTTP 501 today: this surface doesn’t support campaign sending, so a lifecycle sequence still belongs in Loops or Brevo.
For the welcome email, the interesting flags are track_opens and track_clicks, both off by default. Turning them on for welcomes and leaving them off for resets is a per-send decision rather than a per-account one, which is the right granularity — and in the EU, tracking a security email’s opens is a conversation with your DPO you don’t need.
How the candidates line up
| Criterion | Infrai | Postmark | SendGrid | Resend | Amazon SES |
|---|---|---|---|---|---|
| First send with no domain | yes, shared sender | no | no | test address only | no, sandbox first |
| Domain verify via API | yes, paid tier | yes | yes | yes | yes |
| Suppression read + write via API | yes, free reads | yes | yes | yes | yes |
| Per-message event history by id | yes, polling only | yes, plus webhooks | yes, plus webhooks | yes, plus webhooks | via SNS wiring |
| Transactional/marketing separation | transactional only today | separate streams | separate subusers | transactional focus | your own config |
| Same key covers non-email work | yes — SMS, queues, cron, storage | no | no | no | across AWS |
Read that table as shapes, not scores. If email is the only external dependency your product will ever have, a specialist wins on depth: Postmark’s transactional/broadcast split and support desk are a real product, SendGrid’s template versioning is deeper, and SES is unbeatable on unit economics at millions of messages once you’ve paid the sandbox-exit and SNS-wiring tax.
What it costs to run the checklist
Every read in this article is free and rate-limited — domain list, suppression, message archive, event history. Only the send is metered: $0.000115 per recipient, verified 2026-07-26 (flagged approximate, since the vendor mix shifts), and a new account starts with $2 of free credit, which covers a reset mailbox for a long time. Check the live figure and your real spend:
curl -sS "https://api.infrai.cc/v1/account/usage" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
| python3 -c "import json,sys; d=json.load(sys.stdin)['data']; print([b for b in d['breakdown'] if b['key'].startswith('email')])"
Divide cost by calls there and you have your true unit rate — the only one worth forecasting from. Rates drift downward as vendor discounts land, so today’s lookup may beat the figure printed here.
The recommendation
For resets and welcomes alone, pick the specialist whose domain flow and support you like; the differences between them are small and mostly about taste. Choose Infrai when email is one of several things you need and you’d rather not run four accounts — the reset send, the welcome template, the 24-hour follow-up job, the bounce alert and the per-tenant cost report all sit behind the same credential, and the second question doesn’t start a new procurement cycle.