Sending Multilingual Emails with Postmark and Gitloc
Trevor Rawls
Posted on June 21, 2024
In today's global landscape, connecting with customers in their preferred language is crucial. Whether through emails, messaging apps, or websites, communicating in a customer's native language builds stronger relationships and boosts transaction success. This is particularly true for transactional or notification emails, where crucial information can be easily missed if not presented in the recipient's language.
In this post, we’ll dive into our journey of implementing multilingual transactional emails for spotsmap.com, exploring how our approach has evolved over time. Each method has its merits depending on your specific needs. Note: While we use Node.js for the examples, the concepts can be applied across various programming languages.
The Challenge
Spotsmap.com required a localized booking confirmation email that included details like sport types, dates, costs, and course information. The HTML template alone was over 500 lines of code.
First Approach: Language-Specific Templates
Initially, we created separate templates for each supported language. Our code included a configuration object to select the appropriate template based on the user's language. Here’s a simplified example of how it looked:
html
<h2>Arrival date:</h2>
<p>{{ArrivalDate}}</p>
And the code:
javascript
// config.js
export default {
services: {
email: {
postmark: {
apiKey: process.env.POSTMARK_API_KEY,
templates: {
bookingConfirmation: {
'en': 123456,
'de': 123457,
'es': 123458
}
}
}
}
}
}
// services/postmark.js
export const sendMail = ({ from, to, template, locale, params }) => {
const request = client.sendEmailWithTemplate({
From: from,
To: to,
MessageStream: 'outbound',
TemplateId: postmarkConfig.templates[template][locale],
TemplateModel: params || {},
});
request
.then(result => {
console.log(`Email sent to ${to}`);
})
.catch(err => {
console.log(`Email was not sent, with error code ${err.statusCode}, ${err.message}`);
});
}
While straightforward, this approach became cumbersome as we had to replicate changes across multiple templates. When we added a feature to book multiple courses in a single order, updating each language-specific template became a headache.
Second Approach: Conditional Templates
We then moved to a single template with conditionals to handle multiple languages:
html
<h2>
{{#en}}Arrival date:{{/en}}
{{#de}}Ankunftsdatum:{{/de}}
{{#es}}Fecha de llegada:{{/es}}
</h2>
<p>{{ArrivalDate}}</p>
And the updated code:
javascript
// config.js
export default {
services: {
email: {
postmark: {
apiKey: process.env.POSTMARK_API_KEY,
templates: {
bookingConfirmation: 123456
}
}
}
}
}
// services/postmark.js
export const sendMail = ({ from, to, template, locale, params }) => {
const request = client.sendEmailWithTemplate({
From: from,
To: to,
MessageStream: 'outbound',
TemplateId: postmarkConfig.templates[template],
TemplateModel: { ...params, [locale]: true },
});
request
.then(result => {
console.log(`Email sent to ${to}`);
})
.catch(err => {
console.log(`Email was not sent, with error code ${err.statusCode}, ${err.message}`);
});
}
This solution worked well initially but soon became unwieldy as the number of languages and localized strings grew. For 40 languages, this method would lead to a bloated and unmanageable template.
Third Approach: Code-Based Localization
To streamline our process, we shifted localization strings from the template to our codebase, utilizing a standardized translation process similar to what we use for website UI localization.
Our simplified template now looks like this:
html
<h2>{{ArrivalDateTitle}}</h2>
<p>{{ArrivalDate}}</p>
We created JSON files for localization strings, for example, templates/en/bookingConfirmation.json
for English:
json
{
"ArrivalDateTitle": "Arrival date"
}
And the updated sendMail
function:
javascript
// services/postmark.js
export const sendMail = ({ from, to, template, locale, params }) => {
const strings = require(`../templates/${locale}/${template}.json`);
const request = client.sendEmailWithTemplate({
From: from,
To: to,
MessageStream: 'outbound',
TemplateId: postmarkConfig.templates[template],
TemplateModel: { ...params, ...strings },
});
request
.then(result => {
console.log(`Email sent to ${to}`);
})
.catch(err => {
console.log(`Email was not sent, with error code ${err.statusCode}, ${err.message}`);
});
}
To automate the translation process using Gitloc, we created a gitloc.yaml
file:
config:
defaultLocale: en
locales:
- en
- de
- es
directories:
- templates
This approach allowed us to efficiently manage translations by simply updating the gitloc.yaml
file and pushing changes to our remote repository. Gitloc handled the rest, ensuring we could support numerous languages without compromising our codebase’s maintainability.
For more details, check out:
We hope these insights help you streamline your multilingual email process!
Posted on June 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.