Template translations
A template can be sent in several languages. It has one default language and up to 29
language variants (translations). The template ID stays the same in every language, so your
code only adds the language it wants – for example locale: 'de' – and Frontmail picks the right
variant.
Any language works, not just the eight languages of the dashboard: use a
BCP 47 language tag such as hu, nl or pt-BR.
What’s translated and what’s shared
Section titled “What’s translated and what’s shared”Each variant has its own:
- Subject,
- HTML body,
- Text version (generated from the variant’s HTML automatically, or written by hand),
- From name – optional; leave it empty to use the default language’s From name.
Everything else is defined once, in the default language, and shared by all languages: recipients (To, CC, BCC, Reply-To), From email, the parameters, Inline CSS on save, the service, protection settings, attachments, auto-reply and contact collection.
This is deliberate: the security rules of a template – dynamic recipients, the content lock, fixed sender fields and parameter validation – live in one place, so a translation can never loosen them. A variant can only use parameters that exist in the default language.
How to add a language
Section titled “How to add a language”Adding and editing translations needs the Owner, Admin or Developer role.
- Open the template. Above the editor tabs is the Languages bar with the default language (marked Default) and one chip per variant.
- Click Add language.
- Under Language, pick a language from the list or type its code (e.g.
pt-BR) and choose Use “pt-BR”. - Under Start from, choose:
- Copy of the default language – you translate the text yourself,
- Machine translation – translated by your organization’s own provider key (see Machine translation); pick the Provider if you set up more than one,
- Empty – a blank subject and HTML.
- Click Add language, translate or review the content and click Save.
The new language is a draft (marked new) until you save it. Machine translation only uses the saved default language – unsaved changes to it aren’t included.
Editing a translation
Section titled “Editing a translation”Click a language in the Languages bar to switch the editor to it. The editor shows You’re editing the … translation and works as usual:
- Content – the subject, HTML, Visual and Text tabs edit the variant. Sample data and the preview are shared, and the preview renders the variant exactly as it will be sent. Parameters are read-only here (Parameters are shared by all languages. Edit them in the default language.).
- Settings – only the subject, From name and the text-version setting belong to the variant; shared fields are marked Shared by all languages.
- Snippets and Send test have a Language field, so you can copy code or send a test email in any language.
Checks also compare the variant with the default language: a parameter that exists only in the translation is an error (add it to the default language first), a parameter used in the default language but missing in the translation is a warning.
Outdated and Machine badges
Section titled “Outdated and Machine badges”- Outdated – the default language was saved again after this translation was saved, so the translation may miss something. Sending still works. Update the translation and save it, or – if it still fits – click Mark as up to date.
- Machine – the saved version came from machine translation. Review it before you rely on it.
With a variant selected, the bar also offers Translate again (a new machine translation of the saved default language; nothing is saved until you click Save) and Delete language. When you delete a language, its versions are deleted too and emails that ask for it are sent in the default language.
Versions and rollback
Section titled “Versions and rollback”Every save of a translation creates a new version of that translation – each language has its own version numbers. With a variant selected, the Versions tab shows Versions of the … translation with Based on (the default-language version it was saved against) and Origin (Manual or Machine). Compare versions and Restore an older one just like for the default language.
A message always records the language and the translation version it was rendered with. Emails waiting in the hold queue are sent with the versions that were current when they were accepted.
Changing the default language
Section titled “Changing the default language”The default language is under Settings → General → Default language. You can choose any language except one that already has a variant – delete that variant first.
Sending in a language
Section titled “Sending in a language”The language is always chosen explicitly by the sender. Frontmail doesn’t guess it from the
browser (Accept-Language) – pass the visitor’s language yourself when you want it.
How the language is chosen
Section titled “How the language is chosen”For a requested locale, Frontmail uses:
- no
locale→ the default language, - an exact match (case doesn’t matter) → that language,
- the same base language →
de-ATgetsde;ptgets apt-…variant such aspt-BR, - otherwise → the default language.
An unknown language is not an error – locale is a preference. Only a malformed tag (e.g.
"german!") is rejected with 400 invalid_body.
The response tells you which language was actually used:
{ "message_id": "msg_…", "status": "queued", "status_token": "…", "locale": "de" }REST API
Section titled “REST API”Add locale to POST /v1/send (and to each message of POST /v1/send-batch):
{ "template_id": "tpl_01J9…", "template_params": { "name": "Jana" }, "locale": "de"}POST /v1/send-form uses the reserved form field fm_locale. A field named locale stays an
ordinary template parameter, so existing forms keep working:
<select name="fm_locale"> <option value="en">English</option> <option value="de">Deutsch</option></select>Every SDK has a locale option and returns the language used in result.locale.
Browser (@frontmail/browser) – sendForm sends it as fm_locale; a form’s
own fm_locale field wins:
const result = await send('svc_01J9…', 'tpl_contact', { name: 'Jana' }, { locale: 'de' });await sendForm('svc_01J9…', 'tpl_contact', '#contact', { locale: document.documentElement.lang });console.log(result.locale); // 'de'React (@frontmail/react) – the locale prop of <FrontmailForm>, or
options.locale of send / sendForm. Vue and Svelte work the same way:
<FrontmailForm serviceId="svc_01J9…" templateId="tpl_contact" locale="de">…</FrontmailForm>
const { send } = useSendEmail('svc_01J9…', 'tpl_contact');await send({ name: 'Jana' }, { locale: 'de' });Node.js (@frontmail/node):
const { locale } = await frontmail.send({ templateId: 'tpl_order', params: { order_id: 'A-1042' }, locale: 'de' });await frontmail.sendBatch([{ templateId: 'tpl_reminder', params: { name: 'Jana' }, locale: 'cs' }]);Android / Kotlin (dev.frontmail:frontmail):
val result = frontmail.send( "tpl_contact", mapOf("name" to name), options = SendOptions(locale = Locale.getDefault().toLanguageTag()),)println(result.locale) // "de"iOS / Swift (Frontmail for Swift) – use Locale.preferredLanguages.first
(e.g. de-AT); Locale.current.identifier uses _ and isn’t a valid tag:
let result = try await frontmail.send( templateID: "tpl_contact", params: ["name": "Jan"], options: SendOptions(locale: Locale.preferredLanguages.first))print(result.locale ?? "default") // "de"Flutter / Dart (frontmail) – use toLanguageTag() (e.g. de-AT);
Locale.toString() uses _ and isn’t a valid tag:
final result = await frontmail.send( templateId: 'tpl_contact', params: {'name': 'Jan'}, options: SendOptions(locale: Localizations.localeOf(context).toLanguageTag()),);print(result.locale ?? 'default'); // "de"React Native uses send(params, { locale: 'de' }) from useSendEmail. npx frontmail types
lists each template’s languages and generates a type for them – see Node.js.
Auto-replies, resends and EmailJS
Section titled “Auto-replies, resends and EmailJS”- Auto-replies follow the language of the original message: the auto-reply template picks its own variant for that language with the same rules (and falls back to its default language).
- Resend in History sends the message again in the same language, with the translation’s current version. If that language has been deleted since, the default language is used.
- The EmailJS-compatible routes (
/api/v1.0/email/…) and@frontmail/emailjs-compatalways send the default language.
Formatting follows the email’s language
Section titled “Formatting follows the email’s language”The email is rendered in the language it’s sent in. formatDate, formatNumber and
formatMoney format dates, numbers and amounts for that language
(a German variant prints 1. Oktober 2026 and 49,00 €). The Sent via Frontmail footer on the
Free plan is in the email’s language when it’s one of the dashboard’s eight languages, otherwise in
English.
Machine translation
Section titled “Machine translation”Frontmail can translate a template for you with OpenAI, Anthropic, Google Gemini or DeepL – using your organization’s own API key (“bring your own key”). Frontmail never translates on its own account and machine translation doesn’t cost Frontmail credits; the provider bills your account for its usage.
Machine translation is always manual: it runs only when you choose Machine translation in Add language or click Translate again, and the result is a draft you review before saving.
How to set up a provider key
Section titled “How to set up a provider key”Only Owners and Admins can add or change keys. Developers see which providers are set up (read-only) and can use them to translate.
- Open Settings → Machine translation.
- Next to the provider, click Add key. Get a … key opens the provider’s key page.
- Paste the API key. For OpenAI, Anthropic and Gemini you can enter a Model (optional); leave it empty for the provider’s default model. DeepL has no model choice.
- Click Save key. Frontmail checks the key with a small request to the provider before saving it.
With more than one provider, click Make default to choose which one is preselected. Replace key and Remove are next to each configured provider; removing a key keeps the translations you already saved.
The key is stored encrypted and is never shown again – the dashboard shows only its last four characters. Every change is recorded in the audit log.
What is sent to the provider
Section titled “What is sent to the provider”Only the saved default language’s subject, HTML, text version and From name – the text version only when it’s written by hand (an automatic one is generated from the translated HTML). Nothing else leaves Frontmail: no recipients, parameter values, sample data, contacts or message history. The request goes directly from Frontmail’s servers to the provider’s official API, and the provider’s own terms apply to the content.
How the result is checked
Section titled “How the result is checked”Frontmail asks the provider to keep Handlebars expressions ({{name}}, {{#if …}}), HTML tags
and attributes, links and email addresses unchanged, and then verifies it: the translation must
contain exactly the same Handlebars expressions and the same link and image URLs as the source. If
it doesn’t, the result is discarded with The machine translation changed parameters, links or HTML
structure, so it wasn’t used. (translation_invalid) –
try again or use another provider. Nothing is saved partially.
Other errors: translation_not_configured (no key
set up), translation_provider_auth (the provider
rejected the key) and translation_provider_error
(the provider failed – try again later).
Limits and costs
Section titled “Limits and costs”- 30 machine translations per hour per organization.
- 60,000 characters of source content per translation; longer templates get
payload_too_large. - A translation has 60 seconds to finish.
- Costs are charged by the provider to the account the key belongs to. Translating doesn’t use Frontmail credits, and sending a translated email costs the same single credit as any other.
Limits
Section titled “Limits”- 30 languages per template – the default language plus 29 variants.
- A variant has the same size limits as the template’s own content.
- Language tags are up to 35 characters.
Export and import
Section titled “Export and import”Export (.json) includes every language variant, and Import creates them again in the new template. Older export files without variants still import as before.