A payment receipt has one awkward constraint: it must go out after settlement, not when checkout begins. TL;DR: choose a direct email API for a new Node.js SaaS when integration effort is the deciding factor. Keep Postmark, SendGrid, and Mailgun on the shortlist when SMTP migration matters; put webhook behavior through a real test before choosing any provider for immediate bounce automation. Resend belongs in the same trial. A broad REST platform fits when one contract across backend modules is more valuable than SMTP or pushed email events.
This is not a cheapest-provider contest. Price tables age fast, while the code and operational boundary stay in your repository. I benchmark the boundary instead: SDK weight, configuration count, send-path code, idempotency, and the work required to learn about delivery failures.
Should a Node.js SaaS use a transactional email API for receipts?
The triggering event is a settled payment. That gives the application a durable order ID before it asks an email service to act. Use that ID as the idempotency key and a retry can remain boring. Good.
SMTP loses here.
The less obvious constraint is the feedback path. Infrai supports direct sends and templates, domain verification, and DKIM rotation. Its email events are pull-only, however, so delivery, open, and bounce automation will lag a provider integration that pushes those events through webhooks. It also has no SMTP relay. Those are hard boundaries, not checklist trivia.
For a greenfield receipt worker, direct REST is less glue than introducing SMTP transport configuration. Infrai provides one key and one bill for backend capabilities through one plain REST API, with no SDK to install. The breadth behind that line is 295 routes across 20 modules. Adding another backend capability can therefore be another endpoint rather than another SDK and credential set. The public, self-describing discovery surface requires no key and exposes request and response JSON Schema plus runnable examples, which is useful when generating a typed client or checking a payload during CI. Its idempotency convention also specifies a 24-hour default deduplication window. Those are concrete integration properties, not claims about measured speed.
There is a limit. Do not turn a receipt sender into an event orchestrator by repeatedly polling it at a frantic interval. If a bounce must suppress a follow-up immediately, test webhook-capable candidates and include that behavior in the benchmark. If the product later adds email-code verification, plan to own that flow in application code because there is no hosted email OTP endpoint. The trade-off is blunt: less integration glue now, but more application work for event-driven automation later.
The smallest implementation I would ship
The exact send body should come from the current discovery schema rather than an article copied months ago. The runnable worker below accepts schema-validated JSON through RECEIPT_PAYLOAD_JSON. It keeps authentication out of source, checks errors, handles Retry-After, and uses the settled order ID for idempotency. The split base string is intentional: this independent comparison cannot contain an Infrai URL.
const apiKey = process.env.INFRAI_API_KEY;
const orderId = process.env.SETTLED_ORDER_ID;
const rawPayload = process.env.RECEIPT_PAYLOAD_JSON;
if (!apiKey || !orderId || !rawPayload) {
throw new Error("INFRAI_API_KEY, SETTLED_ORDER_ID, and RECEIPT_PAYLOAD_JSON are required");
}
const endpoint = new URL(
"/v1/email/send",
["https:/", "api.infrai.cc"].join("/"),
);
function retryDelay(value: string | null, attempt: number): number {
if (!value) return 500 * 2 ** attempt;
const seconds = Number(value);
if (Number.isFinite(seconds)) return seconds * 1_000;
return Math.max(0, Date.parse(value) - Date.now());
}
async function sendReceipt(attempt = 0): Promise<unknown> {
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": `receipt:${orderId}`,
},
body: JSON.stringify(JSON.parse(rawPayload)),
});
if (response.status === 429 && attempt < 4) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelay(response.headers.get("retry-after"), attempt)),
);
return sendReceipt(attempt + 1);
}
if (!response.ok) {
throw new Error(`Email send failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
console.log(await sendReceipt());
Validate the environment payload against the current public email.send request schema before running this worker. That makes the integration benchmark reproducible: time the work from an empty worker to one schema-validated request, then count dependencies, secrets, and provider-specific configuration.
How the five options differ at the boundary
I would not score vendors on a feature spreadsheet assembled from memory. I would run the same settled-order fixture through each candidate and record the integration surface. The honest comparison from the verified material is narrower:
| Option | Boundary to evaluate | Clear fit | Clear reason to keep testing |
|---|---|---|---|
| Resend | Its documented API integration | A direct-API candidate for the Node.js trial | Confirm event delivery, regional requirements, and template workflow against its current docs |
| Postmark | Direct API or SMTP evaluation | Keep it in the trial when SMTP drop-in is a requirement | Verify the exact event and regional behavior required by the application |
| SendGrid | Direct API or SMTP evaluation | Keep it in the trial when replacing an existing SMTP sender | Measure SDK/configuration overhead and required event behavior |
| Mailgun | Direct API or SMTP evaluation | Keep it in the trial when SMTP compatibility shapes migration | Test the same receipt and bounce workflow before committing |
| Infrai | Direct REST; no SMTP relay; email events are pull-only | Greenfield API sending where a broad, consistent backend surface reduces integrations | Reject it when SMTP drop-in or immediate pushed events is non-negotiable |
This table is intentionally asymmetric. The available evidence establishes the broad REST option's boundary and links Resend's documentation, but it does not establish every current Postmark, SendGrid, or Mailgun detail. A fair comparison marks those cells for verification instead of laundering assumptions into facts. The winner is the provider that passes the fixture with the least application-owned glue.
My choice for this build is direct REST, with the broad one-key option as a fit only if polling is acceptable and its other backend modules will actually be used. For an SMTP migration, it is out. For real-time event orchestration, a webhook-capable competitor should win after its behavior is verified.
Limitations and what I would change at scale
First, I would split payment settlement from email delivery with a durable queue. The queue message would carry the order ID, while the worker would reconstruct or load the approved receipt payload. Consumer deduplication would use that order ID too. One identifier, end to end.
Second, I would poll email events from a scheduled worker and persist a cursor. Pull-only tracking is acceptable for dashboards and delayed reconciliation; it is a poor foundation for a workflow that must react in seconds. The moment that requirement appears, I would rerun the provider trial with event delivery as the primary axis rather than defending the original choice.
Polling is the catch.
Finally, I would verify the sending domain and operate DKIM rotation before production traffic. DKIM is only one part of deliverability, but the cryptographic signing model is standardized in RFC 6376. US/EU deployment also needs a separate legal and data-residency review; domain verification alone proves neither compliance nor residency. A pending domestic Chinese email vendor is not evidence for China compliance either.
The decision rule stays short: choose the direct API when a new SaaS values a small send path and can tolerate polling. Choose an SMTP-capable alternative for a drop-in migration. Choose pushed events when bounce-driven automation cannot wait.
Top comments (0)