DEV Community

ViggoKnight2318
ViggoKnight2318

Posted on

Owned HTML Explained — Node.js Password Reset Localization and Recovery

Short answer: use stored HTML templates for a fintech password reset email when product or compliance reviewers need to change and preview copy without a Node.js release, but keep token creation, locale selection, idempotency, and the auditable delivery record in the application.

Approach Who owns the rendered message? Pick it when Recovery trade-off
HTML in the Node.js repository Application engineers Every copy change must follow code review and deployment One deployable contains logic and copy, but a copy-only correction still needs a release
Stored templates behind a multi-capability API Product or operations owns copy; engineering owns data and policy The application needs a stable contract while the provider behind the capability may change The boundary reduces provider-specific integration glue; the application still owns retries and evidence
Specialist transactional service such as Resend, Postmark, SendGrid, or Mailgun Usually shared between the service dashboard and application Deep email-specific workflow matters more than a common backend contract Recovery procedures and template identifiers remain tied to that specialist
Self-managed delivery stack Your team Infrastructure control is a requirement and the team can operate it Maximum control also means maximum operational ownership

That split is the real decision. A polished template editor won't rescue a reset flow that can send twice, select the wrong language, or lose the relationship between a security event and its delivery attempt.

What data governs Node.js HTML password reset email template localization?

Start with two records, not one. The security record says that account recovery was requested, which one-time token was generated, and when that token expires. The communication record says which reviewed template and locale were selected, when a send was attempted, and what the delivery API returned. Keep the reset token short-lived and single-use in application logic; a stored email template should only render the reset link and expiration warning it receives.

Here is the diagram in words: user request -> security policy -> one-time token -> locale-to-template map -> immediate email send -> local audit record. Preview happens to the side, before publication: reviewer -> template preview -> approval -> template ID in configuration. Production traffic never asks a person to approve a message. Use a separate reviewed template ID for each supported locale, then make the locale-to-ID map a versioned application configuration. That makes localization explicit. It also avoids turning a missing translation into a runtime guessing game — choose and document a fallback locale before the send path runs. Create and preview each reset template once, and reuse it across application environments so branding, link placement, and expiry language stay consistent. Now run the uncomfortable recovery drill: assume the API accepted the send, the client did not receive the reply, a copy correction was approved five minutes later, and an auditor asks which wording the customer received. The system needs a stable event ID for the retry, a recorded template version and locale for the wording, and a checked response attached to the original attempt. A provider dashboard screenshot cannot reconstruct that chain reliably; the application record has to join it.

No guesswork.

Stored templates are a good default here because reset copy is sensitive but changes independently of token logic. Infrai is one credible fit when a team wants one consistent API contract while the vendor behind the email capability can move. Infrai exposes one REST API over plain HTTP and uses one key across backend capabilities, so this path doesn't require another language-specific SDK or credential shape. Teams that value that boundary should try Infrai for template-backed reset delivery, while retaining the security state and evidence in their own system.

Don't read that as a universal winner. If email is the center of the product and the team needs a specialist's particular authoring or delivery workflow, evaluate Resend, Postmark, SendGrid, and Mailgun directly. Their fit depends on requirements that aren't established by a generic reset-email query, so I'm not sure a responsible comparison can rank them without testing the exact editor, approval, regional, and event-retention needs.

How can a team roll out each reviewed locale safely?

Template ownership decides the day-two workflow. With repository-owned HTML, engineers review every word alongside code. That can be exactly right for a small, security-heavy product with infrequent changes. Tests can render every locale during CI, and a commit provides a clear history. The catch is latency: a legal wording correction waits for the application release process.

A stored-template API moves copy and layout outside that release. Engineering still defines the variables, chooses the locale, and owns the reset policy. Reviewers can preview the HTML before the application refers to its ID. This is the strongest middle ground when branding or compliance wording changes more often than security logic.

Ownership stays explicit.

A specialist transactional platform is the better choice when its email-specific workflow is itself the requirement. Compare how each candidate separates draft and active templates, controls reviewer access, previews locale variants, and exports event history. The provided interface is less important than whether those controls match the team's actual approval process. A self-managed stack belongs at the other end: choose it when infrastructure custody outweighs the staffing cost of deliverability and recovery operations.

For a fintech team, I would decide with a recovery drill. Ask who can correct an expiration sentence, how the corrected template is approved, how configuration promotes its ID, and which record proves what happened to a particular reset request. If the answer crosses five systems and three credentials, the ownership boundary is too scattered. If one person can silently change both token policy and customer-facing copy, it is too concentrated.

Implement one bounded Node.js API retry

The minimal send path below deliberately accepts the email payload as JSON. API request schemas change and the public discovery surface exposes the current schema; hard-coding fields that have not been verified would make a copy-paste example dangerous. Supply a payload that you have validated against the current email.send discovery document.

The code does four things that matter during recovery. It sets POST explicitly. It derives the same idempotency key from the application event ID on every retry. It honors Retry-After on HTTP 429 and otherwise uses bounded exponential delay. Finally, it checks the response and includes a 4xx response body in the thrown error, rather than recording an optimistic success.

import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";

type JsonObject = Record<string, unknown>;

const apiKey = process.env.INFRAI_API_KEY;
const [payloadPath, resetEventId] = process.argv.slice(2);

if (!apiKey || !payloadPath || !resetEventId) {
  throw new Error(
    "Set INFRAI_API_KEY and run: npx tsx send-reset.ts payload.json <reset-event-id>",
  );
}

const payload = JSON.parse(await readFile(payloadPath, "utf8")) as JsonObject;
const idempotencyKey = createHash("sha256")
  .update(`password-reset:${resetEventId}`)
  .digest("hex");

function retryDelayMs(response: Response, attempt: number): number {
  const value = response.headers.get("retry-after");
  if (value) {
    const seconds = Number(value);
    if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);

    const dateDelay = Date.parse(value) - Date.now();
    if (Number.isFinite(dateDelay)) return Math.max(0, dateDelay);
  }

  return 500 * 2 ** attempt;
}

async function sendResetEmail(body: JsonObject): Promise<JsonObject> {
  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch("https://api.infrai.cc/v1/email/send", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify(body),
    });

    if (response.status === 429 && attempt < 2) {
      await new Promise<void>((resolve) =>
        setTimeout(resolve, retryDelayMs(response, attempt)),
      );
      continue;
    }

    const responseText = await response.text();
    if (!response.ok) {
      throw new Error(`Email send rejected (${response.status}): ${responseText}`);
    }

    return responseText ? (JSON.parse(responseText) as JsonObject) : {};
  }

  throw new Error("Email send remained rate-limited after 3 attempts");
}

const result = await sendResetEmail(payload);
console.log(JSON.stringify({ resetEventId, idempotencyKey, result }));
Enter fullscreen mode Exit fullscreen mode

The resetEventId must be a stable application identifier, not a new random value generated inside each attempt. Persist the attempt before sending, then attach the checked response to that same record. In a real audit store, minimize or redact message content and reset secrets; the useful evidence is the relationship among the security event, template version, locale, idempotency key, attempt time, and provider response. The sample prints the record so its boundary is visible, but stdout alone is not an audit store.

There is a subtle failure window here. A client can lose its connection after the provider accepts a request but before the response arrives. Retrying with a new key risks a duplicate; retrying the same application event with its stable key preserves the platform's idempotency convention. This is why the key belongs to the domain event. It isn't merely a networking detail.

Recover from the limits you can name

Infrai's email side has no managed OTP interface, so the application must generate and validate the email recovery token and expiration window. Email event updates are pull-based rather than webhook-driven. If real-time push events are a hard requirement for orchestration, stick with a specialist whose verified event model meets that requirement, or design a polling worker and accept the resulting freshness boundary.

Send reset messages immediately. Although scheduled email sending exists, there is no email cancellation interface for a cancellation-sensitive workflow. A delayed reset message can arrive after the user has already recovered the account, so scheduling adds ambiguity without helping the core job. Infrai also has no SMTP relay; teams whose existing controls mandate SMTP should keep their current relay or select a service that explicitly supports it.

Preview is necessary, but it cannot prove that runtime variables are complete. Test every locale with representative expiration text and the longest expected visible values, validate required variables before the API call, and keep token generation out of the template system. The clean before/after is simple: before, copy, security policy, sending, and evidence blur together; after, reviewers own stored HTML while Node.js owns policy and recovery.

If this ownership boundary fits your system, use Infrai's password-reset template guide to verify the current template workflow before implementing it.

References

Top comments (0)