2026年5月26日

2026年5月26日

メールのエンコーディング(Base64・Quoted-Printable)の仕組みと文字化けの読み解き方

メールの本文や件名に日本語や添付ファイルを載せられるのは、MIMEという決まりで「文字コード(charset)」と「転送用のエンコーディング(Content-Transfer-Encoding)」を宣言し、データをメールで安全に運べる文字に変換しているからです。日本語の本文や添付ファイルにはBase64、英数字が中心の本文にはQuoted-Printable(QP)がよく使われます。この記事では、2つの方式の変換の仕組みと違い、件名などのヘッダーの書き方、実際のメールのソースの読み方、文字化けしたときの確認の順番を説明します。

なぜエンコーディングが必要なのか

メールの配送の仕組み(SMTP)は、もともと7ビットのASCII文字だけを運ぶ前提で作られました。メールの形式を定めるRFC 5322でも、1行は改行を除いて998文字までと決められ、78文字以内が推奨されています。そのため、次のようなデータはそのままでは運べないか、途中で壊れるおそれがあります。

  • 日本語のように8ビットのバイトを含む文字
  • 画像やPDFのようなバイナリデータ
  • 改行の無い、とても長い行

いまの多くのメールサーバーは、8ビットのデータを運ぶ拡張(8BITMIME)に対応していますが、経路のすべてが対応している保証はありません。そこでMIME(RFC 2045〜2047)では、データをASCIIの範囲の文字に変換し、その方式をヘッダーで宣言する方法が決められています。

charsetとContent-Transfer-Encodingは別の層

文字化けを理解するうえで最も大事なのは、2つの宣言が別の役割を持つことです。

Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
  • charset(文字コード): 文字をどのバイト列で表すかの決まりです。UTF-8、ISO-2022-JP、Shift_JISなどがあります。
  • Content-Transfer-Encoding(転送エンコーディング): そのバイト列を、メールで運べる文字にどう変換するかの決まりです。7bit、8bit、binary、quoted-printable、base64 があります。

受信側は、まず転送エンコーディングを戻してバイト列を得て、次にcharsetに従って文字に戻します。どちらかの宣言と中身が食い違うと文字化けします。「Base64で文字化けした」という場合も、実際にはcharsetの宣言が間違っていることがよくあります。

日本語のメールでは、以前はISO-2022-JP(いわゆるJISコード)を 7bit で送る形が主流でしたが、現在はUTF-8を base64 か quoted-printable で送る形が一般的です。

Base64の仕組み

Base64は、バイト列を3バイト(24ビット)ずつ区切り、6ビットずつの4つに分けて、それぞれを64種類の文字(A〜Z、a〜z、0〜9、+、/)に置き換える方式です。

たとえば「こんにちは」はUTF-8では15バイトです。

文字列 : こんにちは
UTF-8  : e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af(15バイト)
Base64 : 44GT44KT44Gr44Gh44Gv(20文字)

3バイトが4文字になるので、データの大きさはおよそ4/3(約33%増し)になります。メールではさらに76文字ごとに改行を入れるため、実際にはもう少し増えます。添付ファイルを付けるとメールの容量が元のファイルより大きくなるのはこのためで、メールサーバーの容量制限を考えるときにも影響します。

最後の区切りが3バイトに満たない場合は、足りない分を = で埋めます。Ma(2バイト)なら TWE= のように末尾に = が付きます。

Base64は元のデータの内容に関係なく同じ割合で増えるため、日本語の本文や添付ファイルのように8ビットのバイトが多いデータに向いています。一方で、変換後の文字列は人間には読めません。

Quoted-Printableの仕組み

Quoted-Printable(QP)は、ASCIIの英数字や記号はそのまま残し、それ以外のバイトだけを = と2桁の16進数で表す方式です。

元の文字列 : Café = 5€
QP         : Caf=C3=A9 =3D 5=E2=82=AC
  • é(UTF-8で c3 a9)は =C3=A9 になります。
  • = そのものは記号として特別な意味を持つため、=3D に変換されます。
  • 1行は76文字までで、それを超える行は、行末に = を置いて折り返します(ソフト改行)。受信側は、行末の = と改行を取り除いて元の1行に戻します。

英文のように大部分がASCIIの本文なら、ほとんどそのまま読めるのがQPの利点です。しかし日本語では効率が悪くなります。UTF-8の日本語は1文字が3バイトで、QPでは1バイトが3文字(=E3 など)になるため、1文字が9文字に膨らみます。「こんにちは」をQPにすると =E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF の45文字で、Base64の20文字の倍以上です。

件名や差出人名のエンコーディング(RFC 2047)

ヘッダーには Content-Transfer-Encoding が適用されないため、件名や差出人の表示名に日本語を使う場合は、RFC 2047の「encoded-word」という書き方を使います。

Subject: =?UTF-8?B?44GK5ZWP44GE5ZCI44KP44Gb?=

=? と ?= で囲まれた部分が1つの単位で、中は 文字コード?方式?データ の形です。方式の B はBase64、Q はQPに近い方式で、Q方式では空白を _ で表すなどの違いがあります。上の例は「お問い合わせ」をUTF-8のBase64で表したものです。

1つのencoded-wordは75文字までと決められているため、長い件名は複数に分け、2行目以降を空白で始めて折り返します。受信側は、隣り合うencoded-wordの間の空白を無視してつなげます。

メールのソースを読んでみる

メールソフトの「ソースを表示」や「メッセージのソース」で、実際のメールを確認できます。日本語のHTMLメールなら、おおよそ次のような構造です。

Subject: =?UTF-8?B?44GK5ZWP44GE5ZCI44KP44Gb?=
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="b1"

--b1
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64

44GT44KT44Gr44Gh44Gv
--b1
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable

<p>=E3=81=93=E3=82=93=E3=81=AB=E3=81=A1=E3=81=AF</p>
--b1--

multipart のメールでは、boundary で区切られた部分ごとに、それぞれ Content-Type と Content-Transfer-Encoding を持ちます。同じメールの中でも、テキスト部分はBase64、HTML部分はQPというように方式が混在することがあります。中身を確かめたいときは、該当の部分を取り出して戻します。

echo '44GT44KT44Gr44Gh44Gv' | base64 -d
python3 -c "import quopri,sys; print(quopri.decodestring(sys.argv[1]).decode('utf-8'))" '=E3=81=93=E3=82=93'

文字化けしたときの確認の順番

  1. charsetの宣言と中身が合っているか: charset=ISO-8859-1 と宣言してUTF-8のバイトを送る、のような食い違いが最も多い原因です。ソースのcharsetを見て、デコードした結果をその文字コードで読めるか確認します。
  2. ヘッダーがエンコードされているか: 件名や差出人名に日本語を生のまま入れると、受信側の環境によって化けます。=?UTF-8?B?...?= の形になっているか確認します。
  3. 二重にエンコードされていないか: すでにBase64にした文字列を、さらにBase64にしてしまうと、受信側では変換後の文字列がそのまま表示されます。
  4. **QPのソフト改行や = が壊れていないか**: 本文を自前で組み立て、= を =3D に変換していない、行の長さを守っていない、といった場合に、記号の欠落や不自然な改行が起きます。
  5. 途中で書き換えられていないか: メーリングリストや転送の仕組みが本文にフッターを足すときに、エンコーディングを崩すことがあります。

PHPやWordPressでの扱い

PHPでメールを組み立てる場合、エンコーディングは自前で書くより、ライブラリに任せるほうが安全です。PHPには base64_encode()、quoted_printable_encode()、件名用の mb_encode_mimeheader() などの関数がありますが、ヘッダーと本文の両方を正しく組み立てるのは手間がかかります。

WordPressの wp_mail() は、同梱のPHPMailerでメールを組み立て、サイトの文字コード(通常はUTF-8)をcharsetに使います。PHPMailerの既定の転送エンコーディングは 8bit で、998文字を超える行があるときなどは自動でQPに切り替える処理が入っています。版によって細部は異なります(2026年10月現在)。特定の環境で文字化けが続く場合は、phpmailer_init アクションでエンコーディングを指定できます。

<?php
// wp-content/mu-plugins/mail-encoding.php
add_action( 'phpmailer_init', function ( $phpmailer ) {
	$phpmailer->CharSet  = 'UTF-8';
	$phpmailer->Encoding = 'base64'; // 本文を Base64 で送る
} );

変更する前に、テスト用のアドレスへ送って、Gmail・Outlookなど主要なメールソフトで表示を確認してください。

まとめ

  • charsetは「文字をどのバイトで表すか」、Content-Transfer-Encodingは「そのバイトをメールで運べる文字にどう変換するか」で、別の層の宣言です。
  • Base64は3バイトを4文字にする方式で、容量は約33%増えます。日本語の本文や添付ファイルに向いています。
  • QPはASCIIをそのまま残し、それ以外を =XX で表す方式です。英文には読みやすく効率的ですが、日本語では大きく膨らみます。
  • 件名などのヘッダーは =?UTF-8?B?...?= のencoded-wordで表します。
  • 文字化けは、charsetの食い違い、ヘッダーの未エンコード、二重エンコードの順に確認します。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲