Skip to main content

Localize Authentication Emails and Account-Action Links

2026-09-16

Localize Authentication Emails and Account-Action Links

Localized authentication emails often break when a user needs to trust them. The app is in French, but the verification email arrives in English. The password reset message is translated, but its link opens an English confirmation page. Fixing this requires one account locale that controls the send operation, the email copy, and every state rendered after the user opens the link. This guide defines that contract and provides a verification matrix for registration, reset, recovery, language changes, and cross-device use.

Why authentication email language drifts

Authentication messages do not usually come from the same resource bundle as app screens. An identity provider sends the email, an email client opens it, and a browser or app handles the one-time action code. Each layer can select a language independently.

Four locale inputs appear in a typical flow:

  1. The language currently displayed by the app.
  2. The locale saved on the user's account.
  3. A language setting held by the authentication SDK or provider request.
  4. The browser or device preference used by the hosted action page.

If those inputs have no declared precedence, the result depends on timing. A user can switch the app from English to Spanish, request a reset, and still receive English because the server never received the change. A shared authentication client can retain the previous request's language. A hosted page can ignore the email language and use the browser preference instead.

Firebase makes this boundary visible. Its user-management documentation says you can set the Auth instance's languageCode, or apply the device language, before sending a verification or password-reset email. That setting is operational state. It is not proof that the user's preferred locale has been persisted or that a later server-side send will use the same value.

Treat the locale as part of the account-action transaction, not as ambient UI state.

Define one locale contract before changing templates

Start by writing a precedence rule that every sender and handler can implement. A practical order is:

  1. Use the user's explicit in-app language choice when it is supported.
  2. Otherwise use the validated locale stored on the account.
  3. Otherwise use the app's initial locale negotiation result.
  4. Otherwise use the product's documented fallback locale.

Do not store an arbitrary header or query value as the account locale. Normalize it through the same registry used by the app, then map it to the language identifiers accepted by the email provider. The locale mapping layer should return both the canonical app tag and the provider-specific code.

A compact account record might look like this:

{
  "user_id": "usr_4821",
  "preferred_locale": "fr-CA",
  "locale_source": "in_app_selection",
  "locale_updated_at": "2026-08-06T18:40:00Z"
}

Keep preferred_locale separate from the last device locale. One account may be used on a French phone and an English laptop. An explicit account choice should not change merely because the user opens the app on another device.

Decide when a language change takes effect. New authentication messages can use the latest saved account locale while previously sent links keep the locale captured when they were created. An old email then stays in one language for its full flow.

Inventory every account-action surface

Verification and password reset are only the obvious messages. Build an inventory that includes:

  • New-address verification
  • Password reset
  • Email recovery after an address change
  • Multi-factor enrollment or recovery messages
  • Suspicious-login alerts, if your provider sends them
  • The page shown while an action code is processed
  • Success, expired-code, invalid-code, and already-used states
  • Resend controls and support links
  • The destination shown after completion

Give each item an owner, source copy, translation state, fallback, and test case. Provider templates should use the same approved terminology as the app. If the button says "Verify email" in the product, the email subject, call to action, and completion page should not use three unrelated translations for verification.

This inventory catches partial launches. Teams often translate the email body but miss the subject, preview text, sender name, footer, or hosted error page. A completed locale requires every item in the transaction rather than one HTML template.

Set the locale at the send boundary

Resolve the locale immediately before requesting an email. Do not rely on a global authentication client's previous state. The send path should accept a locale as an explicit input and reject unsupported values before calling the provider.

type AccountAction = "verify_email" | "reset_password" | "recover_email";

type SendRequest = {
  userId: string;
  email: string;
  action: AccountAction;
  requestedLocale?: string;
};

async function sendAccountAction(request: SendRequest) {
  const account = await users.get(request.userId);
  const locale = localeRegistry.resolve(
    request.requestedLocale,
    account.preferredLocale,
    "en"
  );

  const providerLocale = localeRegistry.toAuthProvider(locale);
  const actionUrl = actionLinks.create({
    action: request.action,
    locale,
    returnPath: "/account/security"
  });

  await authEmailProvider.send({
    to: request.email,
    action: request.action,
    language: providerLocale,
    actionUrl
  });

  await audit.record("account_action_email_requested", {
    userId: request.userId,
    action: request.action,
    locale,
    providerLocale
  });
}

This code illustrates application architecture; it is not a Firebase API signature. The provider adapter should translate these explicit inputs into its supported SDK or management API calls.

For client-triggered Firebase sends, set the intended language before calling the send method as described in the Firebase Auth user-management guide. For server-triggered messages, resolve the account locale on the server rather than assuming the client setting will follow the request into another runtime.

Avoid placing translated sentences in the action URL. Carry a validated locale tag and stable state only. The receiving page should load reviewed resources for that locale.

Preserve locale through the action link

The email is only the first screen. The link must open a handler that uses the same locale for loading, success, failure, and the return path. An action link is a deep link carrying a one-time code, so the same rules as preserving locale context across mobile deep links apply here.

Firebase's custom email action handler guide describes action links with a mode, one-time action code, continuation URL, and lang parameter. It also shows separate handlers for password reset, email recovery, and email verification. That separation matters because each mode has different recovery steps and user-facing errors.

At the handler boundary:

  1. Parse the action mode and one-time code.
  2. Validate the locale against the app's supported registry.
  3. Fall back through the documented locale chain if it is missing or unsupported.
  4. Validate the continuation destination against an allowlist.
  5. Load localized copy before displaying progress or errors.
  6. Apply the action through the provider.
  7. Render a mode-specific result and a safe next action.

Do not trust an arbitrary continuation URL from the browser. Store stable destination identifiers when possible, or allowlist schemes, hosts, and paths before redirecting. Localization must not turn the account handler into an open redirect.

A cross-device flow needs special handling. The user may request a French reset on a phone and open the email on a laptop whose browser is set to English. The captured transaction locale should win for the action page. After the action succeeds, the signed-in product can return to the account's latest preference.

Localize failure and recovery states

Account links expire, get reused, and sometimes open in the wrong application context. Treat these as required product states and localize their copy.

Write distinct messages for:

  • Invalid or malformed action code
  • Expired code with a resend option
  • Code already consumed
  • Account disabled or unavailable
  • Network failure before confirmation
  • Provider failure after submission
  • Unsupported locale fallback

Do not expose raw provider errors to users. Map stable error categories to reviewed app messages and keep the original diagnostic in structured logs. The user needs a clear next step, such as requesting a new reset email, returning to sign-in, or contacting support.

Preserve the selected locale when the user requests a replacement email from an expired-link page. Otherwise the first email can be French and the replacement English. Include the action type, resolved locale, template version, and outcome in audit events, but do not log one-time codes or full action URLs.

Test the full account transaction

A template preview cannot prove that the production flow selects the right locale. Run each supported launch locale through the real send and handler boundaries as well.

For every account-action type, cover these cases:

  • A new user verifies an address in the current app language.
  • An existing user requests a reset after changing the app language.
  • The email opens on a device with a different browser language.
  • The link opens in the app and in the web fallback.
  • The action code succeeds, expires, and is reused.
  • An unsupported locale follows the expected fallback chain.
  • A resend keeps the original transaction language.
  • A continuation destination is accepted or rejected correctly.
  • Subjects, body copy, buttons, footers, and handler states use approved terms.
  • Locale and template-version events appear in logs without sensitive codes.

Use seeded test accounts with explicit locale values. Capture the rendered subject and body, extract the action URL without recording its secret code, and assert that its locale and destination are correct. Then exercise the handler with provider test facilities or a controlled test project.

The community question about setting a translated Firebase verification email in a NativeScript app shows why this needs an integration test, not just a documentation check. The developer encountered a read-only SDK property while trying to set French for the message in a concrete mobile authentication flow. Wrappers and platform bindings can expose provider language controls differently. Your adapter test should prove the language reaches the actual send operation for every supported client stack.

Watch for implementation mistakes

Deriving every message language from the current device silently changes account communication when a user signs in elsewhere. Persist an explicit preference and record its source.

Avoid mutating one shared authentication client for concurrent server requests. Request A can set French, request B can set German, and the eventual sends can race. Prefer a per-request provider client or an API that accepts language as a request argument. If the SDK only exposes mutable state, isolate access and test concurrency.

Templates and action pages need the same release state because users experience them as one path. An English expired-code screen after a Spanish reset email is still a localization failure.

Fallback can hide missing work. Emit a metric when a requested locale uses another template or handler bundle. Alert on sudden fallback increases by action type and app version.

A successful provider response proves only that the request was accepted. It does not prove delivery or correct rendering. Keep delivery diagnostics separate from locale correctness, and avoid claiming that localization changes inbox placement.

Put the flow into the release gate

Add account-action coverage to the same locale release checklist as app UI and storefront assets. A locale is ready only when templates are reviewed, provider codes are mapped, action pages are deployed, and the transaction matrix passes.

Start with one action today. Pick password reset because it exercises an unauthenticated user, a one-time code, a hosted or app handler, error states, and a return to sign-in. Trace the locale from account storage to provider request, email copy, action URL, handler resources, and final destination. Fix the first point where it changes. Once that path is deterministic, apply the same contract to verification and recovery instead of building separate language logic for each message.

References