Lesstep
Japanese email · RFC 2047 · Checked October 29, 2026

Japanese email subjects that arrive garbled

If your app writes the email itself, for example the raw message it passes to the Gmail API's messages.send, a Japanese subject or sender name written into the header as plain UTF-8 can arrive as mojibake. Email headers are ASCII. Text outside ASCII goes in RFC 2047 encoded-words, such as =?UTF-8?B?5bGx55SwIOWkqumDjg==?= for 山田 太郎:

const enc = new TextEncoder();

function mimeWords(text) {
  const words = [];
  let part = "";
  for (const ch of text) {
    const next = part + ch;
    if (part &&
        enc.encode(next).length > 39) {
      words.push(part);
      part = ch;
    } else {
      part = next;
    }
  }
  words.push(part);
  return words.map((w) =>
    "=?UTF-8?B?" +
    Buffer.from(w).toString("base64") +
    "?=").join("\r\n ");
}

Each encoded-word holds at most 39 bytes of UTF-8, 13 Japanese characters, and never splits a character. RFC 2047 allows an encoded-word of at most 75 characters and says "A multi-octet character may not be split across adjacent 'encoded-word's" (RFC 2047); RFC 5322 says a header line "SHOULD be no more than 78 characters" (RFC 5322). Buffer is Node.js's. In the From line, the address goes on a line of its own, so a long name doesn't push the line past 78. A name longer than 13 Japanese characters takes two encoded-words, and Python's parser read ours back with a space where they join, so keep the sender's name short.

const raw = [
  "From: " + mimeWords("山田 太郎") +
    "\r\n <taro@example.com>",
  "To: " + to,
  "Subject: " + mimeWords(subject),
  "MIME-Version: 1.0",
  "Content-Type: text/plain;" +
    " charset=UTF-8",
  "Content-Transfer-Encoding: base64",
  "",
  Buffer.from(body).toString("base64")
    .replace(/.{76}/g, "$&\r\n"),
].join("\r\n");

// For the Gmail API
const msg = {
  raw: Buffer.from(raw)
    .toString("base64url"),
};

The Gmail API takes the whole message "in an RFC 2822 formatted and base64url encoded string" (its reference); it doesn't encode the headers for you. We ran both blocks as written in Node.js 24 and read the results back with Python's email package: 75 subjects, from one character to a long one with kanji, emoji, half-width kana and English, all decoded back exactly, with no encoded-word over 75 characters, no header line over 78, and no character split. We did not send them through Gmail.

What went wrong in real apps

Some readers cope with raw UTF-8 in a header (Python's parser read our 請求書をお送りします, "we are sending the invoice", back correctly), but the ones above didn't.

Let your mail library do it

Libraries that build the message encode these headers for you. Python's EmailMessage, given msg["Subject"] = "請求書をお送りします" and msg["From"] = "山田 太郎 <taro@example.com>", wrote both as =?utf-8?b?...?= encoded-words. You need mimeWords() only where you write the header lines yourself: raw messages for the Gmail API or Amazon SES, or SMTP by hand.

Shorten the subject before you encode it, by characters: cutting the bytes splits a Japanese character. libredesk #688 cut subjects at 100 bytes, and the replies came back with U+FFFD in them (how to cut by characters).

UTF-8, not ISO-2022-JP

Older Japanese software sends ISO-2022-JP, a 7-bit Japanese encoding: EC-CUBE #1450, a Japanese e-commerce package, sends ISO-2022-JP-MS by default. Mail apps don't all read it: Nextcloud Mail #13472 reports ISO-2022-JP mail garbled. For a new app, send UTF-8, as Python's EmailMessage does.

The rest of a Japanese check

An order confirmation with a garbled subject looks like spam to the person who ordered. The same pass catches Enter while typing Japanese (the fix), yen charged 100 times over (the fix), kanji drawn in their Chinese shapes (the fix), headings that break in the middle of a word (the fix), postal codes rejected when typed in Japanese (the fix), dates saved one day early in Japan (the fix), address forms in the American order (the fix), phone numbers sent to +81 with the 0 still in (the fix), PDFs with the Japanese missing (the fix), search that finds nothing in Japanese (the fix), previews cut down to “...” in Japanese (the fix), lists sorted in the wrong order (the fix), CSV files that open garbled in Excel (the fix), Japanese titles that give an empty URL slug (the fix), file names garbled on upload and download (the fix), dates in the Japanese era that come out wrong (the fix), full-width spaces that get past your checks (the fix), public holidays your app gets wrong (the fix), and vertical text that comes out sideways or upside down (the fix).

Lesstep translates web apps into Japanese and delivers them as one pull request. In Complete (US$249), we check every screen in Japanese, the emails your app sends included, and tell you where a subject or a sender's name arrives garbled. The first step is a free sample.

Gmail and Google are trademarks of Google LLC; Amazon SES is a trademark of Amazon.com, Inc. or its affiliates; Python is a registered trademark of the Python Software Foundation; Nextcloud is a trademark of Nextcloud GmbH; Windows is a trademark of Microsoft Corporation; Node.js is a trademark of the OpenJS Foundation. This page is not affiliated with or endorsed by any of them.

More: Enter sends the message while typing Japanese · Stripe charges 100 times too much in yen · Kanji in their Chinese shapes · Japanese headings that break in the middle of a word · Forms that reject numbers typed in Japanese · Dates that come out wrong for Japanese users · Address forms in the Japanese order · Japanese phone numbers in forms · Japanese text missing from your PDFs · Search that finds nothing in Japanese · Length limits that break in Japanese · Japanese lists sorted in the wrong order · Japanese CSV files that open garbled in Excel · Japanese titles that give an empty URL slug · Japanese file names garbled on upload and download · Japanese era dates that come out wrong · Full-width spaces that get past your checks · Japanese public holidays your app gets wrong · Japanese vertical text that comes out sideways or upside down · Japanese localization checklist · Japanese localization for your web app