意外と知らないメールの送受信周りのこと | Dev Driven 開発・デザインチーム 意外と知らないメールの送受信周りのこと | 働くひとと組織の健康を創る iCARE

BLOG

意外と知らないメールの送受信周りのこと

中村一星
2020/05/22

こんにちは、いっせいです

先日ActionMailerのテストをしていて解決方法を探っていく中で新しく知ったことがあったのでご紹介したいと思います。

きっかけの出来事

きっかけはメールの本文中に意図する文言が含まれているかどうかを検証するテストがたまに失敗するということがあったことです。

ざっくり以下のようなコードとテストを書いていました。
(イメージです)


class SampleMailer < ApplicationMailer
  def send_mail
    body = build_body
    mail from: "[Carely] <no-reply@mail.carely.io>", subject: "title" do |format|
      format.html { render 'sample_mailer/send_mail' }
    end
  end
end

describe SampleMailer do
  context "failed test" do
      let!(:mail) do
        SampleMailer.send_mail
      end

      it "期待する文字が本文が含まれている" do
        expect(mail.body.encoded).to include("hogehoge")
      end
  end 
end

結論からいうとテストがたまに失敗する原因は mail.body.encodedencoded メソッド使っていたのがよくなかったのですが、そこに気づくまでに初めて知ったことが2つありました。

失敗するときには以下のようにテストが落ちていました
(イメージです)

expected "<html><body>=E5=90=88=E8=B3=87=E4=BC=9A=
  +=E7=A4=BE=E6=B2=B3=E9=87=8E=E6=B0=B4=E7=94=A3<br/>itjtwuj2up=E6=A7=98<br/=
  +><br/>=E3=81=8A=E4=B8=96=E8=A9=B1=E3=81=AB=E3=81=AA=E3=81=</body></html>" to include "hogehoge"
  Diff:
  @@ -1,2 +1,418 @@
  -hoghoge
<html>
<body>
<div>
  + =E5=90=88=E8=B3=87=E4=BC=9A=
  +=E7=A4=BE=E6=B2=B3=E9=87=8E=E6=B0=B4=E7=94=A3<br/>itjtwuj2up=E6=A7=98<br/=
  +><br/>=E3=81=8A=E4=B8=96=E8=A9=B1=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=
  +=8A=E3=82=8A=E3=81=BE=E3=81=99=E3=80=82<br/>Carely=E5=81=A5=E5=BA=B7=E8=A8=
  +=BA=E6=96=AD=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88=E3=83=87=E3=82=B9=E3=82=AF=
  + </div>
   +</body>
  +</html>

日本語が知らない形式で文字化けしていたのです。

Quoted Printable というエンコード方式

ASCII_8BIT(BINARY)でエンコードされているのか?
URLエンコードされているのか?
似ているけど、どちらもイコールで始まらないよな?
URLエンコードなら < なども変換されるよな。。。?
といろいろ調べていた時、文字化けした本文をこちらのサイトでデコードしてみることにしました。

文字化け解読ツール「もじばけらった」

ただ一つ正しくデコードできたエンコードがありました。
それが Quoted-Printable でした
BASE64と同じく非ASCII文字をASCII文字で表現する方式だそうです。

知らなかった....

またメールのエンコード・デコードはヘッダーの
Content-Transfer-Encoding
で設定されるということも知りました。

RFC も読もう

しかしテストコードは毎回上記のように失敗するわけでなく、数回に一度失敗していました。

mail.bodyMail::Message オブジェクトだったので
mail gem の中身を覗いてみました。
(使用しているバージョンは2.6.6)

encode メソッドからデコードに使うエンコードを判定している箇所を読み進めていくと興味深いことがかいてありました。

https://github.com/mikel/mail/blob/2.6.6/lib/mail/body.rb#L157
https://github.com/mikel/mail/blob/2.6.6/lib/mail/body.rb#L144
https://github.com/mikel/mail/blob/2.6.6/lib/mail/encodings/8bit.rb#L29-L32

      # Per RFC 2821 4.5.3.1, SMTP lines may not be longer than 1000 octets including the <CRLF>.
      def self.compatible_input?(str)
        !str.lines.find { |line| line.length > 998 }
      end

コメントに「SMTPでは改行を含む一行は1000文字を超えてはいけない」とありました。
https://www.ietf.org/rfc/rfc2821.txt
これも初めて知りました。
SMTPのRFCを読んだことがなかったので初めて知りました。
今回のトラブルを直接解決するものではありませんが、やはり仕様をしっておくのは大切ですね。

ちなみに

今回の箇所ではメール本文をHTMLで組んでいたので見た目は改行していても一行で書かれている箇所があり、
テストデータによっては1000文字を超えてしまうことがありました。
これによって encodeメソッドはQuoted-Printable でエンコードしていたのです。

そもそも本文の中身を検証するのにencodedを使っていたのがよくなかったのでto_s を使うことで解決をしました
https://github.com/mikel/mail#encodings

おわりに

トラブルシューティングはいろいろなことを教えてくれますねw

みなさんもお使いの技術に関するRFCや仕様書などを見てみると新しい発見があるかもしれませんよ!

ではまた!