Multilingual Email
Send emails in the recipient's preferred language using template translations.
Send emails in the recipient's preferred language using template translations.
Language resolution order
Owlat resolves the language for each email in this order:
- Explicit
languageparam in the send request - Contact preferred language stored on the contact record
- Template default language chosen when you create the template (and, for marketing templates, changeable later in Settings)
- Fallback default (
en)
You can control language at different levels depending on your needs.
Option 1: Explicit language per request
Pass language directly when you know the user's locale:
import { Owlat } from '@owlat/sdk-js'
const owlat = new Owlat('lm_live_...')
await owlat.transactional.send({
slug: 'welcome',
email: 'mira@acme.io',
dataVariables: { firstName: 'Mira' },
language: 'de',
})
curl -X POST https://your-deployment.convex.site/api/v1/transactional \
-H "Authorization: Bearer lm_live_..." \
-H "Content-Type: application/json" \
-d '{
"slug": "welcome",
"email": "mira@acme.io",
"dataVariables": { "firstName": "Mira" },
"language": "de"
}'
Option 2: Stored contact preference
Each contact can have a stored Language preference. When you send without an explicit language param, the resolver falls back to this value (see the resolution order above).
A contact's language is not part of the create API — owlat.contacts.create({ ... }) (and the underlying POST /api/v1/contacts) only accept email, firstName, and lastName. You set the stored language one of two ways:
- Dashboard — edit the contact and set the Language field.
- CSV import — include a
languagecolumn when importing contacts.
A transactional send does not store the request's language on the contact — even when the send creates a brand-new contact. The request language is recorded only on the send record; a new contact is created with no stored language preference.
Once a contact has a stored language, you can omit language and let the resolver use it:
// No language param — the resolver uses the contact's stored preference,
// then the template default, then 'en'.
await owlat.transactional.send({
slug: 'welcome',
email: 'mira@acme.io',
dataVariables: { firstName: 'Mira' },
})
If your application already knows the recipient's locale (from your own user record), passing language on every send is the most predictable approach — see Option 1. The stored preference is best when the language is managed inside Owlat (dashboard or import).
Full example: read locale from your database
A complete handler that reads the user's locale preference and sends in the right language:
import { Hono } from 'hono'
import { Owlat } from '@owlat/sdk-js'
const app = new Hono()
const owlat = new Owlat(process.env.OWLAT_API_KEY!)
app.post('/api/send-notification', async (c) => {
const { userId, templateSlug, variables } = await c.req.json()
// 1. Look up user locale from your database
const user = await db.users.findUnique({
where: { id: userId },
select: { email: true, firstName: true, locale: true },
})
if (!user) return c.json({ error: 'User not found' }, 404)
// 2. Send with the user's preferred language
const result = await owlat.transactional.send({
slug: templateSlug,
email: user.email,
dataVariables: {
firstName: user.firstName,
...variables,
},
language: user.locale ?? undefined,
})
return c.json({ messageId: result.transactionalEmailId })
})
You need to add translations for each language in the Owlat dashboard before sending. If a requested language doesn't have a translation, the template default language is used. See Translations for setup instructions.
Next steps
- Translations guide — add translations to your templates
- Transactional API reference — language resolution details
- Welcome Email — basic transactional sending pattern