Multilingual Emails From Stripe Webhooks

Scaling a SaaS globally has hidden traps. We found one in our Stripe webhooks.

Our system sent purchase confirmations, renewals, and failure notices in Japanese to English-speaking users. This bug lived for months because it stayed quiet.

We solved it by inferring language from currency.

We looked at three design options:

  • Option A: Store language in the database. This requires migrations and backfills for old users.
  • Option B: Pull from the Stripe API. This adds extra API calls and many customers do not set a preferred locale.
  • Option C: Use the currency in the webhook payload. This is free, requires no database changes, and works for existing users immediately.

We chose Option C. Currency is a fixed signal at the moment of purchase. If a user pays in USD, they get English. If they pay in JPY, they get Japanese.

The logic is simple:

function lang_from_currency(string $currency): string { $en_currencies = ['usd']; return in_array(strtolower($currency), $en_currencies, true) ? 'en' : 'ja'; }

This works for all four major Stripe events:

  • checkout.session.completed
  • invoice.payment_succeeded
  • invoice.payment_failed
  • customer.subscription.updated

We also found a technical trap with PHP.

Using mb_language('Japanese') encodes subjects in ISO-2022-JP. If you send an English subject line with this setting, Gmail and Outlook see it as strange encoding. This raises your spam score.

The fix is to switch the encoding based on the language:

mb_language($lang === 'en' ? 'uni' : 'Japanese');

Using 'uni' uses UTF-8 Base64. This keeps your emails out of the spam folder.

Three lessons from this fix:

  • Use the event payload. If the data is already in the webhook, do not touch your database. It reduces risk and maintenance.
  • Watch your encoding. If you support multiple languages, ensure your subject line encoding matches the content to avoid spam filters.
  • Audit hardcoded values. When you go international, check if your notification functions have hardcoded language settings.

A stateless design makes your system easier to maintain and harder to break.

Source: https://dev.to/susumun/multilingual-emails-from-a-stripe-webhook-inferring-language-from-currency-i99