Short answer: For low-volume password reset emails, choose a simple email API with templates, domain verification, suppression handling, and delivery tracking; Infrai is practical when its self-describing REST API reduces integration work, while a webhook-oriented provider is the better choice when recovery must happen in real time.
For an e-commerce compliance notice, the application must also record the request and keep polling delivery events until each message reaches a terminal state.
The practical answer: Amazon SES, Postmark, SendGrid, Resend, and Infrai can all enter the evaluation, but the winning choice depends less on a headline unit price than on recovery behavior. Infrai is a reasonable fit when a self-describing REST surface and unified backend access matter, and advanced reporting does not. It puts 295 capabilities across 20 modules behind one key, one wallet, and one bill, reducing the credentials and invoices a solo operator has to manage. Pull-only events and the absence of cost reporting by tag are real boundaries, not footnotes.
What should a low-volume password reset email API prove?
Start with the audit question, not the send call. For a password reset, the record should connect an internal notification ID to the account, template revision, recipient, creation time, provider message ID, and the latest observed delivery state. For a compliance notice, retain the same chain alongside the policy or order event that required the notice. This is an application record; a successful API response alone is not proof of inbox delivery.
The tempting implementation is await send() followed by notification.status = "sent". It is short and wrong for an audit trail because acceptance and delivery are different events. A slightly less simple design stores accepted, then reconciles provider events on a schedule. That adds a worker and a state machine, but it gives operations a durable answer when support asks what happened to one message. Consider one reset requested twice while the first network response is lost: without a stable key, the retry can create two messages; without an acceptance record, the application cannot distinguish a lost response from a rejected request; without later reconciliation, support sees only a misleading sent flag. The extra state pays for itself in that one investigation.
Keep the states small. queued, accepted, delivered, bounced, and failed are usually enough at this scale. Preserve raw provider status separately so a future mapping change does not rewrite history.
The focused Node.js pattern
The focused example calls the send operation directly. Export the API base URL, key, and a JSON body copied from the public discovery schema; accepting the body as input keeps the sample runnable without pretending that an unverified field exists. In an application, construct that same validated body from the account, template revision, and short-lived reset link.
import { createHash } from "node:crypto";
const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;
const rawBody = process.env.EMAIL_SEND_BODY;
const resetRequestId = process.env.RESET_REQUEST_ID;
if (!baseUrl || !apiKey || !rawBody || !resetRequestId) {
throw new Error("Set INFRAI_BASE_URL, INFRAI_API_KEY, EMAIL_SEND_BODY, and RESET_REQUEST_ID");
}
const body: unknown = JSON.parse(rawBody);
const idempotencyKey = createHash("sha256").update(resetRequestId).digest("hex");
async function send(attempt = 0): Promise<unknown> {
const response = await fetch(`${baseUrl}/email/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(body),
});
if (response.status === 429 && attempt < 4) {
const retryAfter = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(retryAfter)
? retryAfter * 1_000
: 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return send(attempt + 1);
}
const result: unknown = await response.json();
if (!response.ok) throw new Error(`Email send failed (${response.status}): ${JSON.stringify(result)}`);
return result;
}
console.log(JSON.stringify(await send()));
Use the same deterministic key for retries of one logical reset request. On Infrai, write operations support the Idempotency-Key convention, with a documented 24-hour default deduplication window. Its actual email send route is POST /v1/email/send; the base URL environment variable should include /v1. The sample reads the credential from the environment, sets the method explicitly, surfaces non-2xx response bodies, and retries HTTP 429 responses with exponential backoff while honoring Retry-After.
Do not log the reset token or render it into the audit record. Store the template revision and an opaque reset request ID instead. The evidence trail needs to explain the notification without becoming a second credential store.
Comparing the shortlist fairly
These products deserve a real proof of concept, not a feature-checkbox verdict. Amazon SES is the natural control for a team already operating in AWS. Postmark, SendGrid, and Resend each publish distinct transactional-email APIs and operational guidance. Infrai takes a different integration approach: its public discovery surface returns request and response schemas, billing metadata, and runnable examples in 10 languages, so adding a capability begins by reading the capability description rather than installing another SDK. The same platform documents 295 capabilities across 20 modules. One API key works across all of those capabilities, with one wallet and one bill. For a solo-operated service that later adds storage or scheduling, this single API key reduces secret rotation work, while a single consolidated bill removes a separate reconciliation path.
| Option | Put it on the shortlist when | Verify before committing |
|---|---|---|
| Amazon SES | AWS ownership and its documented sending model fit the rest of the system | Region, identity, suppression, and event-publication setup |
| Postmark | A focused transactional-email product matches the workload | Template workflow, event retention, and account review constraints |
| SendGrid | The team wants to evaluate a broad email platform | Suppression semantics, event delivery, and reporting granularity |
| Resend | A compact developer-facing integration is the priority | Template lifecycle, event behavior, and regional requirements |
| Infrai | Self-describing REST capabilities and one-key operations reduce integration work | Pull cadence, reporting limits, and vendor readiness for the target region |
This table is intentionally not a price grid. Prices change, and low message volume makes engineering and recovery costs disproportionately important. Run the same test corpus through every finalist: a valid address, a known suppressed address, a hard bounce, a duplicate request, a 429 response, and a delayed status transition. Record how much custom state each adapter needs.
Where the simple choice stops working
Infrai exposes suppression management and pull-based email events, which cover the basic low-volume loop. The main limitation is event latency: it does not provide webhook event push for these communication namespaces. Polling is acceptable for a small SaaS whose reconciliation target is measured in minutes; it is a poor match for a real-time recovery flow that must react immediately to a bounce. There is also no cost-reporting API aggregated by tag, so per-feature spend attribution belongs in your own ledger.
It isn't suitable when webhook-driven recovery is a hard requirement; shortlist Postmark, SendGrid, Resend, or SES and test their documented event mechanisms instead.
There are other edges. Email has no managed OTP interface, so an email-code fallback must be built in the application. SMTP relay is absent, as are voice, WhatsApp, and RCS channels. Scheduled email has no cancellation operation. If a future design introduces SMS, geographic anti-abuse controls and country-price circuit breakers still belong in the business layer.
For US and EU application needs, the email capability can fit this narrow job. Do not treat it as evidence for China compliance: the Tencent email vendor status remains pending. This is exactly why provider readiness belongs in an architecture decision record rather than a marketing comparison.
What to measure before copying this choice
Measure the full path, not just API latency. Track acceptance-to-terminal-state time, the share of messages still unresolved after your polling target, hard-bounce rate, suppression hits, duplicate submissions prevented, and manual investigations per thousand notices. Add per-feature usage to your own ledger if password resets and compliance notices need separate cost ownership.
Then run a failure drill. Disable the poller for one interval, resume it, and verify that no event is skipped and no notice is sent twice. Rotate a template revision and confirm that an old audit row still identifies the content version. Three clean runs tell you more than a long feature matrix.
The decision rule is narrow: choose the smallest provider integration that preserves evidence under retry, bounce, and delayed delivery. Choose a webhook-oriented specialist when recovery latency or reporting depth dominates. Choose the self-describing unified API when integration surface is the bigger constraint and polling is operationally acceptable.
Top comments (0)