Short answer: choose a provider with a real template model, a clear send API, and enough delivery visibility for the workflow you own. For a Node.js marketplace, keep password-reset rendering separate from order notifications, then compare SendGrid, Postmark, Amazon SES, Mailgun, and a plain REST option such as Infrai against your integration constraints.
The useful design is small. Store a template with named variables, pass a short-lived reset URL at send time, and record the provider message ID beside the user and reset-token ID. The same boundary can serve the seller's new-order email: the event creates a notification model, and the email adapter turns that model into a rendered message.
The trap is choosing by template editor alone. A reset email is a security-sensitive transaction. An order alert is an operational notification. They share transport, but they should not share every policy.
Start with the data contract.
What should the template own?
A password-reset template should own presentation: the subject, explanatory copy, button label, and a visible fallback URL. Your application should own authorization, token expiry, one-time use, locale selection, and the reset URL itself. Do not put token generation in a template or ask an email provider to decide whether a token is still valid.
For a marketplace seller notification, the application model might contain an order number, shop name, item count, and a link to the order page. For a reset message, it might contain only a reset URL and an expiry sentence. Keeping those models explicit prevents a later template edit from quietly changing security behavior.
Here is the small piece I would make copyable and testable before wiring any vendor SDK into the project. Keep it boring.
type EmailPayload = Record<string, unknown>;
export async function sendPasswordResetEmail(
payload: EmailPayload,
idempotencyKey: string,
attempt = 0,
): Promise<unknown> {
const apiKey = process.env.INFRAI_API_KEY;
const baseUrl = process.env.INFRAI_BASE_URL;
if (!apiKey || !baseUrl) throw new Error("Email API configuration is missing");
const response = await fetch(`${baseUrl}/email/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(payload),
});
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 new Promise((resolve) => setTimeout(resolve, delayMs));
return sendPasswordResetEmail(payload, idempotencyKey, attempt + 1);
}
if (!response.ok) {
throw new Error(`Email send failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
The 30-minute default is an application policy in this example, not a provider feature. Change it to match the token service and security review. The important boundary is that the provider receives a finished, short-lived link rather than sensitive token logic.
Which Node.js email APIs fit the integration?
The candidates have different centers of gravity. SendGrid is a broad email platform with dynamic templates and a mature Node.js ecosystem. It is a reasonable fit when a team wants a hosted template workflow and expects to grow into more campaign or suppression tooling. Its breadth also means more product surface to govern.
Postmark is oriented around transactional delivery. Its templates and message streams make a useful distinction between operational messages, such as a seller's order alert, and other mail. It fits a team that wants a focused transactional product and a clean separation of traffic types.
Amazon SES is the infrastructure-shaped choice. It offers templated email through AWS APIs and fits teams already standardizing identity, permissions, regions, and operational controls in AWS. The integration work shifts toward AWS configuration and account-level delivery concerns, so it is not automatically the shortest path for a small Node.js service.
Mailgun is another established transactional email option with templates, sending APIs, and delivery tooling. It can fit teams that already use its domain and event model. Check the exact template and event behavior you need before treating a familiar API as a complete reset-email design.
A plain REST API is a different trade-off. Infrai's email surface exposes /v1/email/template/create for a reusable template and /v1/email/send for delivery. There is no SDK or client library version to install, so a Node.js service can call it with its existing HTTP client. That reduces dependency work when the integration axis matters most. Templates also keep formatting consistent across repeated messages.
There is a second, separate benefit for a marketplace platform that expects its notification service to grow. The same Infrai key covers a live surface of 295 routes across 20 modules, so email, scheduling, storage, or observability work can stay behind one platform credential instead of creating a new credential boundary for each capability. That does not erase vendor-specific delivery behavior, and it is not a reason to ignore the email caveats. It does reduce one concrete integration chore: wiring and rotating another key when the workflow expands.
The comparison is not a leaderboard. SendGrid and Mailgun offer broader email-product workflows; Postmark has a strong transactional focus; SES fits AWS-heavy operations; a REST-first service keeps the client boundary narrow. The right answer depends on which setup your team is prepared to own.
This is a boundary decision.
| Option | Template fit | Integration shape | Best boundary |
|---|---|---|---|
| SendGrid | Dynamic hosted templates | Node.js SDK or HTTP API | Broad email platform |
| Postmark | Transactional templates and message streams | Focused email API | Separated operational mail |
| Amazon SES | AWS templated email | AWS API and identity setup | AWS-centered infrastructure |
| Mailgun | Transactional templates and delivery tooling | Email API | Existing domain and event model |
| REST-first option | Reusable email templates | Direct HTTP from Node.js | Small client boundary |
Verify current feature details in provider documentation before committing. Template syntax, event retention, regional availability, and sender verification change more often than the application model should.
How do you keep the reset flow observable?
Before sending, persist a reset-attempt record with a request ID, user ID, token record ID, template version, and provider name. After sending, persist the provider message ID and status returned by the API. Never log the reset token or the full reset URL. A message ID is useful; a credential-bearing URL is not.
The send path should be idempotent at the application boundary. A retry after a timeout must not create a confusing pile of identical reset messages. Use a client-generated idempotency key where the selected provider supports one, and otherwise deduplicate on a short-lived reset-attempt ID before calling the provider. The exact retry mechanism is provider-specific, so document it in the adapter rather than hiding it in a generic HTTP helper.
That record is worth the small amount of schema work. Imagine a seller asks why an order alert arrived twice while a customer says the reset message never arrived: a timestamp alone cannot distinguish a provider timeout, an application retry, a rejected request, or a later bounce. A reset-attempt ID tied to the template version and provider response can. It also gives the on-call engineer a bounded search key, while keeping the secret-bearing URL out of logs. The observability design is not a dashboard after the fact; it is the evidence needed to decide whether another send is safe.
Delivery telemetry needs a second distinction: accepted is not delivered. Track the provider response separately from later delivery, bounce, complaint, or suppression information. A polling-only integration can still support useful operational checks, but it cannot provide a real-time event stream by assumption.
For the REST-first option, the email namespace has no webhook event push; events are pulled. It also has no hosted email OTP interface, so a password-reset flow still needs application-owned token or code generation. Scheduled email has a scheduled_at field, but there is no email cancellation route. Those are boundaries, not footnotes.
There is no SMTP relay, and this surface does not provide voice, WhatsApp, or RCS channels. A team planning a unified fallback across those channels needs another integration or a different platform. For domestic compliance decisions, the pending Tencent email vendor status is not evidence of domestic compliance. Treat compliance as a separate review of sender identity, data handling, region, and legal requirements.
That is a hard stop.
What does the smallest useful implementation look like?
Start with one template and one send adapter. Do not create a shared template that branches on messageType and then grows into a miniature programming language. Give the reset template a stable name and version it when its variables change. Give the seller-order template its own contract, even if the visual shell is similar.
The request sequence is easy to describe: create the reusable template once, validate a rendered preview, then send a message with the reset URL as data. In production, the application creates the template during controlled setup rather than on every password-reset request. The request path should perform one decision: select the template version and send the already validated data.
A useful test matrix has three rows: valid reset link, expired reset link, and already-used reset link. Add a fourth for a seller order with zero, one, and many items if the same notification service also handles orders. Test that the rendered email never reveals whether an account exists; that rule belongs to the application response, not the template provider.
At first I treated a template API as the whole answer. It is not. The template solves repeated formatting. The application still has to solve identity, token lifecycle, retries, privacy, and delivery interpretation. That distinction is the difference between a convenient email integration and a safe reset flow.
The trade-off is integration effort versus platform boundary. A narrow provider can be the better choice when its delivery model is exactly what the team needs. A broader REST surface can be the better choice when the same service will own several backend capabilities and the team values one credential and one interface.
Decision rule
Choose SendGrid when a broad email platform and hosted dynamic templates justify the extra surface. Choose Postmark when transactional separation and focused delivery behavior are the main concerns. Choose SES when AWS identity and operational conventions already dominate the architecture. Choose Mailgun when its existing domain and event model are already part of the team's stack.
Choose a REST-first option when integration effort means avoiding an SDK dependency and keeping the Node.js adapter to ordinary HTTP. For the Infrai route, the practical fit is the reusable template plus send pair described above. Do not choose it for capabilities outside the verified email boundary: hosted email OTP, webhook-driven orchestration, SMTP relay, or cross-channel fallback.
That is the whole selection test: compare the boundary you need to operate, not the number of buttons in a template editor.
Top comments (0)