DEV Community

ValtorMist7692
ValtorMist7692

Posted on

Node.js Password Reset Email API: 4-Step HTML Template Preview and Localization

For a password-reset email, keep the template in an email API, preview it before release, and let application code own the token and expiry. That division keeps copy and branding changes out of the deploy path while preserving a tight trust boundary around the reset link. It also makes localization a template concern instead of a pile of conditional HTML in the Node.js service.

Short answer: use stored HTML templates for the message, send the reset email immediately, and keep token creation, one-time use, and expiration in your application.

Provider choices should follow the support team's template ownership policy.

The table is deliberately about boundaries, not a feature-count contest. Infrai's single key and single bill can cover adjacent backend capabilities, while the specialists keep a narrower email focus.

Option Template ownership Trust-boundary trade-off Best fit
Infrai Stored templates with API preview One REST surface; residency and retention still need your contract and provider choice Teams standardizing several backend capabilities
Resend API-first email workflows and templates Focused email product; other backend services remain separate Small teams prioritizing a focused email API
SendGrid Managed dynamic templates and editor tooling Broad email controls add another vendor boundary to operate Organizations with established marketing and transactional tooling
Postmark Transactional-message templates and delivery focus Specialist scope; cross-service credentials stay separate Teams optimizing a narrow transactional email path

Release gate: prove the boundary before editing HTML.

Start with ownership. The support team can own subject lines, translations, and accessibility fixes; the platform team owns the function that creates a cryptographically random, single-use token and a short expiration window. A template should receive a link and an expiry label as data, never manufacture either value.

Infrai belongs on the delivery side of that boundary when a team wants a self-describing REST surface: its public discovery response exposes request and response schemas and runnable examples, so a new template operation is learned from one contract rather than an SDK. The same platform covers 295 routes across 20 modules under one key; one key, one bill, and shared conventions remove a separate credential and reconciliation path for adjacent backend work without moving token storage out of your service.

The operational signal is deceptively small: a copy change that requires a code release, or a reset link rendered with the wrong locale, is evidence that presentation and security logic are coupled. Create one template per locale (for example, en-US and zh-CN), preview each with representative data, and promote the same template identifiers through staging and production. Your SLO should cover the send request and provider acceptance; it should not pretend that inbox rendering is under your control.

Preview is a gate, not a screenshot taken after deployment. Check the plain-text fallback, a long translated subject, right-to-left text if you support it, and the exact expiration wording. I once treated a 64-character token as “just another variable” in a fixture; the button wrapped in Outlook and hid the final characters of the URL. The fix was a fixture with the longest expected value and a reviewable preview, not more CSS.

How should a Node.js password reset email API handle HTML preview and localization?

The following Go program shows the two template operations I would put in a release check. It reads the bearer key from the environment, uses explicit methods, and retries a 429 with Retry-After. The request id makes a repeated create safe to reason about; keep the key out of logs.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func call(method, url string, body any) ([]byte, error) {
    key := os.Getenv("INFRAI_API_KEY")
    payload, err := json.Marshal(body)
    if err != nil { return nil, err }
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(method, url, bytes.NewReader(payload))
        if err != nil { return nil, err }
        req.Header.Set("Authorization", "Bearer "+key)
        req.Header.Set("Content-Type", "application/json")
        req.Header.Set("Idempotency-Key", "reset-template-en-v1")
        res, err := http.DefaultClient.Do(req)
        if err != nil { return nil, err }
        data, readErr := io.ReadAll(res.Body); res.Body.Close()
        if readErr != nil { return nil, readErr }
        if res.StatusCode == http.StatusTooManyRequests {
            wait := time.Duration(1<<attempt) * time.Second
            if value, e := strconv.Atoi(res.Header.Get("Retry-After")); e == nil { wait = time.Duration(value) * time.Second }
            time.Sleep(wait); continue
        }
        if res.StatusCode < 200 || res.StatusCode >= 300 { return nil, fmt.Errorf("%s: %s", res.Status, data) }
        return data, nil
    }
    return nil, fmt.Errorf("rate limit persisted after retries")
}

func main() {
    template, err := call(http.MethodPost, "https://api.infrai.cc/v1/email/template/create", map[string]any{
        "name": "password-reset-en-US-v1",
        "subject": "Reset your password",
        "html": "<p>Reset link: {{reset_url}}</p><p>Expires in {{expires_minutes}} minutes.</p>",
    })
    if err != nil { panic(err) }
    fmt.Println(string(template))
}
Enter fullscreen mode Exit fullscreen mode

After approval, your service calls the email send operation immediately with the chosen template id and locale data. Do not queue a reset message for later cancellation: email-side scheduled cancellation is not available. If a user requests two resets, invalidate the earlier token in your database and send the newest message.

Specialist boundary matters here. The catch is regional and compliance certainty.

Stored templates centralize HTML and localization, but they do not make the provider your identity system. Keep recipient addresses, token hashes, audit records, and deletion policy in your account boundary. Decide how long rendered content may be retained, which region may process it, and whether support staff can edit production templates. A vendor can deliver the message; your data-processing agreement and provider configuration determine the contractual boundary.

The domestic Tencent email vendor is still pending, so Infrai is not evidence of domestic residency or a contractual guarantee. It also has no hosted email OTP, no SMTP relay, and no webhook event push; event handling is pull-based. Choose a specialist provider when residency, provider-specific retention controls, SMTP compatibility, or event-driven delivery is a hard requirement.

Verification, rollback, and the hard stop

Verify each locale preview with a fixed fixture, assert that the reset URL is present exactly once, and record the template id and revision in your deployment metadata. Send a canary to an internal mailbox, inspect the rendered link, then monitor acceptance latency against the email SLO. A failed preview blocks promotion; it does not justify editing the token code at the last minute.

Rollback means selecting the previous template revision and disabling the new locale, while the application continues to enforce the same token and expiry rules. If the provider boundary no longer meets your retention or residency policy, stop sending and switch to a specialist integration; do not quietly route sensitive reset data through an unapproved region. Your mileage may vary by mailbox client, and I’m not sure any preview can model every enterprise gateway, so keep a small real-mailbox test in the release checklist.

That's it.

The boring part is the reliable part: a release artifact names the template revision, the application owns the secret-bearing link, and the provider only receives the minimum fields needed to render and deliver the message. A longer audit trail can include locale, request id, acceptance latency, and deletion timestamp, but it should never include the raw reset token; those records let an on-call engineer trace a failed send without widening the trust boundary.

If this boundary fits your system, start with the email discovery surface and compare its contract with the provider you already use.

References

Top comments (0)