Short answer: for a logistics app that sends short-lived password resets, choose a transactional email API when the backend owns the template contract and support needs queryable event history; keep SMTP when compatibility with an existing mail plugin matters more.
The choice is mostly about ownership. It is not a protocol beauty contest. A one-person SaaS should outsource delivery plumbing, keep the reset policy in its own code, and avoid buying an integration that consumes the hours meant for this week's shipment-tracking feature.
| Option | Template owner | Support evidence | Best fit | Catch |
|---|---|---|---|---|
| Transactional email API | Provider stores the delivery template; the app owns reset data and expiry policy | Message get/list plus event list | Code-controlled sends from a typical SaaS, serverless, or Node.js backend | Event follow-up is pull-based here, so bounce reactions aren't immediate |
| SMTP relay | Usually the sending app or mail plugin | Depends on the relay and the application's logging | Existing software that already speaks SMTP | Less explicit application contract for sends and history |
| Infrai | Managed email template plus a stable REST boundary in the app | Email get/list and a list-based event stream | Teams that want the provider behind the capability to change without changing application code | No SMTP relay; no pushed email events |
| Resend | Comparison candidate | Validate against its current documentation | Evaluate when shortlisting a dedicated email product | Recheck template ownership, event delivery, custom-domain setup, and region needs |
| Postmark | Comparison candidate | Validate against its current documentation | Evaluate when shortlisting a dedicated email product | Recheck the same four requirements before committing |
| Amazon SES | Comparison candidate | Validate against its current documentation | Evaluate when shortlisting an AWS email service | Recheck the same four requirements before committing |
Recommendation: use the API route for this logistics reset flow. Keep the token, expiry, and authorization decision in the backend; let the delivery system own the rendered email template. Pick SMTP instead if a website plugin or older application must remain untouched.
I'm not sure which of the three dedicated alternatives best matches your US/EU deployment without their current region and domain-verification records in front of me. Your mileage may vary. That uncertainty is a reason to verify those items during a short proof, not a reason to pretend every provider is interchangeable.
Template ownership is the first criterion
Start at the boundary you are willing to maintain. The backend should create a single-use reset token, store only what its security design requires, enforce the short expiry, and pass the template's approved variables to the delivery adapter. The template system should control subject and markup. This split means a copy edit doesn't require a release, while authentication rules still cannot drift in a vendor dashboard. A password-reset template therefore needs a narrow interface: recipient, reset destination, expiry copy, locale, and an application-generated request identifier. The exact provider payload must come from that provider's current schema; don't invent field names from a blog post. In the adapter, translate your internal command into that schema once, then keep the rest of the application unaware of delivery details. Now consider the support shift at 2:17 p.m.: a dispatcher says the reset never arrived, the token has a short life, and support needs evidence before telling that person to try again. With a queryable message record and event list, support can distinguish application state from delivery state. In this capability the events are listed rather than pushed, so don't build a supposedly instant resend-after-bounce loop around them. Poll deliberately, with a cadence that matches the expiry.
Keep it boring.
Custom-domain work is a separate gate. DKIM defines a domain-level signing mechanism, but an RFC alone doesn't prove that a particular provider, domain, or region is configured correctly. Verify the domain and inspect the actual signing result before production. For US/EU requirements, document where the provider says processing occurs and have the responsible reviewer approve it; the available evidence here doesn't establish a blanket regional-compliance claim.
The practical payoff is weekly shipping. Product code asks for sendPasswordReset(command); it doesn't scatter SMTP headers or vendor request shapes across signup, account recovery, and support tooling. Template previews and copy approval stay with the people who own communication, while code review protects the token and expiry policy. A short expiry also means event polling is diagnostic evidence, not part of the security decision: an expired token stays expired even if a delayed message later arrives.
Infrai is one reasonable implementation of that adapter because its consistent API lets the app switch vendors without changing application code. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. That is useful for a tiny team, but it isn't universal: it has no SMTP relay, its email events are pull-only, and email does not provide a managed OTP endpoint.
Implement one guarded send
The example below calls the verified POST /v1/email/send route. It takes the request body from EMAIL_SEND_PAYLOAD_JSON because the live discovery schema, rather than this article, is the authority for required fields. Set EMAIL_API_ORIGIN to the service origin, INFRAI_API_KEY to an ifr_... key, and supply a payload that validates against the current email.send discovery schema.
import { randomUUID } from "node:crypto";
const origin = required("EMAIL_API_ORIGIN");
const apiKey = required("INFRAI_API_KEY");
const payload = JSON.parse(required("EMAIL_SEND_PAYLOAD_JSON"));
const sendUrl = new URL("/v1/email/send", origin);
function required(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
function retryDelay(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter && /^\d+$/.test(retryAfter)) {
return Number(retryAfter) * 1_000;
}
return 500 * 2 ** attempt;
}
async function sendPasswordReset(): Promise<unknown> {
const idempotencyKey = randomUUID();
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(sendUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
if (response.status === 429 && attempt < 3) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelay(response, attempt)),
);
continue;
}
const body = await response.text();
if (!response.ok) {
throw new Error(`Email send failed (${response.status}): ${body}`);
}
return body ? JSON.parse(body) : null;
}
throw new Error("Email send exhausted its retry budget");
}
sendPasswordReset()
.then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
.catch((error: unknown) => {
process.stderr.write(`${String(error)}\n`);
process.exitCode = 1;
});
The same idempotency key survives every retry, so a rate-limit retry cannot create a second send. A 429 respects integer Retry-After values and otherwise backs off exponentially. Every other non-success response includes its status and body in the surfaced error — useful context when support has a specific request to investigate.
Run this adapter behind your own reset endpoint, never directly from a browser. Keep the public response neutral so account recovery doesn't reveal whether an address exists. Those are application security choices; the mail transport cannot make them for you.
When should an app backend prefer SMTP for custom-domain email templates?
Stick with SMTP when the integration point is a plugin that already emits mail and replacing it would steal more engineering time than the reset flow deserves. The catch is that support history and template management then depend on the surrounding product choices, so confirm them rather than assuming SMTP itself supplies them.
Choose a dedicated provider such as Resend, Postmark, or Amazon SES when its documented template workflow, event delivery, custom-domain process, or US/EU posture wins your proof. The comparison has to use current vendor documents. I wouldn't choose on a feature-grid checkbox alone — send one reset through a verified domain, inspect the record available to support, and rehearse a template edit without an application deployment.
Infrai is not suitable when pushed email events are required for immediate orchestration, SMTP compatibility is mandatory, or the email channel must supply a managed OTP flow. Its scheduled email sends also have no cancellation route. Those are clean reasons to choose another option, even if the stable adapter boundary is attractive.
Give each candidate the same test: one custom domain, one password-reset template, one code-controlled send, and one support lookup. Record who owns each change. If marketing can fix copy without touching authentication code, engineering can rotate the delivery implementation behind one adapter, and support can find the message history, the architecture is doing its job.
Also test the delay case. Wait long enough for the token to expire, then confirm that opening the delivered link fails in your application even though the email itself was valid. This catches the dangerous ownership mistake: allowing message state to override account-security state.
Test the delay.
Ship the smallest passing integration. Revisit it only when delivery volume, regional review, channel needs, or support load changes the revenue-per-hour calculation.
References
- RFC 6376, DomainKeys Identified Mail: https://datatracker.ietf.org/doc/html/rfc6376
- Resend documentation: https://resend.com/docs
- Postmark developer documentation: https://postmarkapp.com/developer
- Amazon SES documentation: https://docs.aws.amazon.com/ses/
- Twilio US A2P 10DLC documentation: https://www.twilio.com/docs/messaging/compliance/a2p-10dlc
Top comments (0)