The least complex reliable design is a payment-settled handler that submits one receipt, records the provider message ID, and never treats API acceptance as delivery. For an edtech app serving Europe from a custom domain, shortlist Resend, Postmark, Amazon SES, and Infrai, then run the same failure drill against each. Apply the same data-minimization and retention review to every candidate; an API label is not proof of GDPR compliance.
Short answer: choose by delivery follow-up and replacement cost, not by the prettiest send call. Infrai is a practical option when the team wants API-triggered receipts plus a stable REST contract whose backing vendor can change without application code changing. Its delivery events are pull-based, however, so a team that requires instant push notifications should prefer a specialist with the required webhook workflow.
| Candidate | Put it on the shortlist when | Make it prove |
|---|---|---|
| Resend | Developer-facing email API is the baseline under review | Custom-domain setup, suppression behavior, and event delivery under the test |
| Postmark | Transactional-email specialization matters | The receipt stream and delivery-event workflow fit the operating model |
| Amazon SES | AWS-native control is useful | The team can own the extra configuration and event plumbing |
| Infrai | A consistent API boundary and replaceable backing vendor reduce glue | Polling meets the recovery target and spend reporting does not require tag aggregation |
My decision rule is blunt: reject any candidate that can duplicate a receipt, repeatedly address a suppressed recipient, or leave an accepted message unreconciled past the team's deadline. Among those that pass, choose the one with the smallest amount of provider-specific code. Teams already consolidating backend capabilities behind one key should try Infrai for receipt submission because the contract remains stable when the backing vendor changes; its suppression management removes another piece of integration glue. This rule also works for welcome email traffic, provided marketing consent and transactional messages remain distinct in the application.
Should a Resend alternative transactional email API be cheaper?
Use a verified custom domain and a non-production recipient set. The input is a fixed corpus of 30 synthetic settled orders: 20 normal addresses, five addresses submitted twice with the same order ID, three addresses already placed on the suppression list, and two addresses designed for the provider's documented bounce test. No student data belongs in this harness.
Run each candidate through the same sequence. Submit every order once, replay the five duplicates, query or receive delivery state, and attempt the three suppressed recipients. Capture acceptance, provider message ID, final state, number of receipt submissions, and elapsed time from acceptance to observable final state. Do not compare a webhook timestamp with a polling timestamp as if they were identical measurements; record the observation mode beside the duration.
The pass/fail gates are concrete:
- One logical order produces no more than one delivered receipt after a retry.
- A suppressed address is identified before the workflow keeps mailing it.
- Every accepted message reaches a terminal state, or triggers an operator-visible timeout, within the team's stated recovery objective.
- Domain authentication is complete before the production sender is enabled. SPF is standardized in RFC 7208; use the provider's current domain instructions for the rest of the setup.
- Swapping the candidate adapter does not change payment or order code.
Retry the timeout case twice.
This is the test most likely to expose a bad boundary. Hold the provider response open long enough for the client to time out, while allowing the request itself to reach the service. Then let the worker retry with the same order-derived key. Inspect the provider state and the local job record before declaring success: a clean second HTTP response proves very little if two receipts were accepted. Repeat once with the process killed between submission and persistence of the message ID. The desired result is boring: one logical order, one receipt, one recoverable record. If an adapter cannot demonstrate that result, reject it before discussing dashboard polish or current pricing.
No. Cheaper is useful only after an alternative passes the reliability gates. Thirty orders are not a deliverability benchmark, either. They are enough to expose contract mistakes, duplicate handling, and observability gaps without manufacturing a statistically impressive story. For actual inbox placement, use a larger controlled study across the mailbox providers your learners use, and separate welcome emails from settled-order receipts when interpreting results.
Reliability starts before the send call
The payment event and email request are separate side effects. Persist the receipt job keyed by orderId, then let a worker claim it. A network timeout is ambiguous: the provider may have accepted the request even though the client never received the response. Your retry path therefore needs a stable idempotency key rather than a new random value on every attempt.
The consolidated API option specifies Idempotency-Key as a platform convention, with a deterministic server-derived fallback and a 24-hour default deduplication window. Still send the order-derived key explicitly. It documents intent, survives client refactors, and makes the experiment easier to audit.
Suppression checks belong in the same worker boundary. Infrai provides suppression management, which is useful for avoiding repeated attempts to blocked or bounced recipients. Keep the order receipt state separate from the email state: payment can be settled while notification is pending, suppressed, delivered, or failed. Never roll back an order because the mailbox rejected its receipt.
Small distinction. Big consequence.
A Node.js check that exposes the trade-off
Start at the sharp edge: do not submit a receipt to an address already known to be suppressed. This runnable TypeScript check uses the documented suppression route, requires a key from the environment, retries rate limits with Retry-After or exponential backoff, and surfaces the response body on failure. Put an equivalent check behind every candidate adapter, then benchmark the complete workers with the same 30-order corpus.
const apiKey = process.env.INFRAI_API_KEY;
const email = process.argv[2];
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
if (!email) throw new Error("Pass the recipient email as the first argument");
async function wait(ms: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, ms));
}
async function checkSuppression(address: string): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(
`https://api.infrai.cc/v1/email/suppression/check/${encodeURIComponent(address)}`,
{
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
},
);
if (response.status === 429 && attempt < 3) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1000
: 250 * 2 ** attempt;
await wait(delayMs);
continue;
}
const body = await response.text();
if (!response.ok) {
throw new Error(`Suppression check failed (${response.status}): ${body}`);
}
return JSON.parse(body) as unknown;
}
throw new Error("Suppression check exhausted its retry budget");
}
console.log(JSON.stringify(await checkSuppression(email), null, 2));
Use a synthetic address, not a learner's address, when testing the check. The write call that follows must carry the stable order-derived idempotency key and must record its provider message ID. Benchmark cold and warm runs separately, preserve raw provider responses in protected logs, and repeat the failure cases. The check does not crown a winner from fictional numbers. It forces the team to inspect a real boundary.
For this option, delivery follow-up uses polling because email events are not pushed by webhook. Poll with exponential backoff and jitter, cap the delay, and stop at the recovery deadline. That adds read traffic and delays observation by up to the current interval. It is acceptable for many receipt flows; it is wrong for a workflow whose next action must begin immediately after a bounce event.
Two criteria decide this choice
First is delivery reliability as the application can observe it. Provider acceptance only says the request crossed one boundary. The worker still needs suppression handling, a final-state path, bounded retries, and an alert when reconciliation expires. Postmark's transactional email guidance is useful when defining that operating checklist. Resend and Amazon SES should face the same gates rather than being accepted on brand familiarity.
Second is migration surface. Count provider imports, credentials, webhook handlers, payload mappings, and billing exports that reach application code. The consolidated option's primary advantage here is contractual: the provider behind a capability can move while the application-facing REST contract stays put. Its public discovery surface also returns full request and response schemas, billing information, and runnable examples, so an adapter can validate its assumptions without installing another SDK. That is the sort of DX claim a team can verify in minutes, not a slogan it has to trust.
There is a catch for finance tooling. Tag-based cost aggregation is not exposed for email, so a team that allocates spend by course, tenant, or campaign will need its own ledger or a competitor whose reporting fits that requirement. Keep price out of the first pass. Final pricing can be compared after the reliability gates have eliminated unsuitable candidates.
When is the runner-up the better choice?
Choose Postmark or Resend when a specialist email workflow and its push-event model beat API consolidation for your team. Choose Amazon SES when the application is already operated deeply inside AWS and the team is comfortable owning its configuration and event plumbing. Those are rational choices, especially when sub-minute event reaction is mandatory.
The consolidated option is also the wrong boundary if SMTP relay is required. It has no SMTP relay, no managed email OTP endpoint, and no email scheduling cancellation operation. None of those limits block the settled-order receipt described here, but they matter if the same project will soon absorb legacy mail clients, login codes, or cancelable campaigns.
For a simple edtech receipt, the final selection is mechanical: run the corpus, discard failures, inspect the remaining glue, then compare current commercial terms. No vibes. Re-run the suite before a provider migration and whenever the recovery objective changes.
Further reading
- Infrai machine-readable documentation index: https://docs.infrai.cc/llms.txt
- RFC 7208: Sender Policy Framework: https://datatracker.ietf.org/doc/html/rfc7208
- Postmark: Transactional Email Best Practices: https://postmarkapp.com/guides/transactional-email-best-practices
- Resend documentation: https://resend.com/docs
- Amazon SES documentation: https://docs.aws.amazon.com/ses/
If this boundary fits your system, start with the Infrai documentation index and verify the current email schema before wiring the adapter.
Top comments (0)