For a game account recovery flow, integration effort is the constraint that changes the answer. Short answer: use a transactional email API with a verified sending domain and a template, while your application owns one-time reset tokens and links. That division keeps the security decision in code you can test and keeps mail delivery in a replaceable service.
The tempting first version is an SMTP relay plus a hand-built HTML string. It works until domain authentication, bounce handling, and template edits become three separate maintenance jobs. An API call gives the game backend a clear boundary: create the token, check suppression, send the message, then poll delivery events.
What should a Node.js password reset email flow own?
Your application should generate a cryptographically random, one-time token, store only a suitable digest with an expiry and account reference, and invalidate it after a successful reset. The email contains a link back to your game, not a token-validation service managed by the mail provider. There is no managed email OTP endpoint in this capability group, so an email-code fallback is also application work.
I keep the link short-lived and single use. I also make the reset endpoint return the same outward response for an unknown address as for a known one; that avoids turning the form into an account-enumeration oracle. The provider should receive a rendered template and recipient data, while password policy, token hashing, and replay checks stay behind your API.
Here is the shape of the sending boundary. The payload fields are deliberately kept in one adapter so changing providers does not leak through the rest of the codebase.
import crypto from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
function makeResetToken() {
const raw = crypto.randomBytes(32).toString("base64url");
const digest = crypto.createHash("sha256").update(raw).digest("hex");
return { raw, digest, expiresAt: Date.now() + 15 * 60 * 1000 };
}
async function sendResetEmail(to: string, resetUrl: string, requestId: string) {
const baseUrl = process.env.INFRAI_BASE_URL ?? "https://api.example.invalid/v1";
const response = await fetch(`${baseUrl}/email/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": requestId,
},
body: JSON.stringify({
to,
template_data: { reset_url: resetUrl },
}),
});
if (response.status === 429) throw new Error("rate limited; retry with backoff");
if (!response.ok) throw new Error(`email request failed: ${response.status}`);
return response.json();
}
const token = makeResetToken();
// Persist token.digest and token.expiresAt with the account before sending.
await sendResetEmail(
"player@example.com",
`https://game.example/reset?token=${encodeURIComponent(token.raw)}`,
"reset-account-1234",
);
In production, the adapter should implement exponential backoff and honor Retry-After for 429 responses. The idempotency key matters because a retry must not create two reset messages. Your storage transaction should also make the token write and request identifier durable before the send is attempted.
I initially treated the mail call as the easy part. Then I mapped a real recovery attempt: a player submits an address, the app writes the digest, suppression is checked, the template is rendered, and a worker polls events later. Each handoff needs a durable request ID. A 429 at the send boundary should schedule a retry with the same ID; a bounce should feed suppression before the next request; an expired token should be rejected even if the message is still sitting in an inbox. That sequence is longer than the fetch call, which is why I keep it in an adapter and test it with fake event responses. The result is a flow that can swap providers without rewriting the account-security code, while still making the provider-specific work visible in one place.
How do custom domain, DKIM, SPF, and template choices affect delivery?
Verify the sending domain first, then publish the DKIM and SPF records that the provider gives you. DMARC (RFC 7489) is the policy layer that tells receiving systems how to evaluate alignment and what to do with failures. Treat these DNS records as deployment configuration, with review and rollback, rather than as a last-minute dashboard click.
Templates are a useful seam between security logic and copy. Keep the reset URL as data, escape it in the template, and avoid putting account secrets or recovery answers in the message. A template update can then change branding without changing token code. The provider supports template creation and updates through its API, but the game still owns token generation and validation.
Which transactional email API fits an integration-first game team?
There is no universal winner. SendGrid offers a broad email product and mature template tooling; Mailgun is often chosen for API-oriented sending and event inspection; Amazon SES integrates tightly with AWS identity and networking; Postmark focuses on transactional delivery and a constrained product surface. Their authentication, event APIs, and template models differ enough that an adapter is worthwhile.
| Option | Integration shape | Where it fits | Trade-off |
|---|---|---|---|
| SendGrid | API plus templates and account tooling | Teams wanting a broad email suite | More product surface to govern |
| Mailgun | API-first sending and event data | Developers prioritizing API control | You still design token and suppression logic |
| Amazon SES | AWS-native API and domain setup | AWS-centered operations | More AWS configuration and IAM decisions |
| Postmark | Transactional-focused templates and delivery | Small, focused recovery mail flows | Less useful when you need a wider messaging suite |
| Infrai | One REST contract spanning multiple backend capabilities | A solo team adding mail beside other services | No SMTP relay, no managed email OTP, and events are pull-based |
Infrai's practical advantage here is breadth behind a simple surface: one REST API covers several backend capabilities under one contract, so adding a related service means another endpoint rather than another SDK integration. Infrai also uses one key for those capabilities, with one bill. Infrai accepts plain HTTP requests without an SDK, which keeps this adapter portable across languages. That is an integration argument, not a promise of better inbox placement.
The verified positioning is one REST API for the entire backend, with one key. For a solo game team that may add storage, scheduling, or an AI support tool beside account mail, that common contract reduces the number of client conventions to document and test. It does not remove the need to understand each capability's limits, and it does not turn delivery events into a push stream.
The catch is operational timing. Email events are pull-based rather than webhook-pushed, so a dashboard or worker must poll GET /v1/email/event/list to classify delivery and bounces. There is no SMTP relay, and there is no email OTP endpoint. Stick with SES when AWS-native controls are a hard requirement, Postmark when a focused transactional product is the priority, or Mailgun when its event workflow matches your operations better.
Keep the polling interval modest.
What should you measure before copying this design?
Measure time from reset request to accepted send, delivery and bounce rates by domain, duplicate-send rate during retries, suppression-hit rate, and the percentage of tokens redeemed before expiry. Break those numbers down by game region and mail provider. I’m not sure a single provider will stay the best fit as your player base grows; your own event data should decide that.
Keep DNS verification, template changes, and token policy in separate deployment reviews. A reset email is a small feature with a large trust boundary. Ship the narrow adapter, observe it, and only then add secondary channels.
References
- https://datatracker.ietf.org/doc/html/rfc7489
- https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API
- https://docs.sendgrid.com/for-developers/sending-email/api-getting-started
- https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/messages
- https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html
- https://postmarkapp.com/developer/api/overview
Top comments (0)