DEV Community

Trevor Rawls
Trevor Rawls

Posted on

Sending Multilingual Emails with Postmark and Gitloc

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>
Enter fullscreen mode Exit fullscreen mode

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}`);
    });
}

Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}`);
    });
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

We created JSON files for localization strings, for example, templates/en/bookingConfirmation.json for English:

json
{
  "ArrivalDateTitle": "Arrival date"
}

Enter fullscreen mode Exit fullscreen mode

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}`);
    });
}
Enter fullscreen mode Exit fullscreen mode

To automate the translation process using Gitloc, we created a gitloc.yaml file:

config:
  defaultLocale: en
  locales:
    - en
    - de
    - es
  directories:
    - templates
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)