💬

SendGridからbounceメールを取得して、sisimaiで解析してみる

2021/12/22に公開
2

この記事は ユアマイスター AdventCalendar2021 の21日目の記事です。
こんにちは Itsuoです。2021年11月から ユアマイスターにjoinしました。

バウンスメールを解析したい

メールの配信時に一番困るのは、登録されたメールアドレスがbounceしてしまうこと。
特にキャリアがdocomoのユーザーがドメイン許可していない場合でも、帰ってくるbounceメールには 'User Unknown'と表記されていること。
なにかこれを解析する手立て無いかと色々と見ていたら、sisimaiというライブラリがあるので、こちらでなんとかできないものかと試してみました。

※ユアマイスターでは SendGridを使用していますが、正確にはSMTP Apiを使用しているので今回のものには、当てはまらないのですが、テストとしてやってみたいと思います。

(先に言っておきますが、友達がすくない私は周りにドコモの人がいなかったので、ドコモでの検証はできていません、、涙)

2021/12/23追記
中の方から すべてが転送されるわけではないとのご指摘いただきました。
そこのところは boundce Apiとか使って整合性をあわせないといけないですね

SendGridで配信していることを想定し、Inbound Parseを利用して、boundメールを取得・解析してみたいと思います。

以下が今回の流れです

※実際にはメール発出サーバとInbound Parseの受け取りは同じサーバの必要はありませんがここでは便宜的に同じにします。

boundメール受け取る準備(SendGrid編)

Inbound Parseの設定

Settings -> Inbound Parseをクリック

Receiving Domainの subdomainには 今回は"parse"とします。
parse.認証済み設定のドメインはDNSにてmxレコードを設定しています)

Domainにはすでに認証済みのドメインがあるので、それを選択します。
Destination URLにはPOSTにて送信されるJsonをパースするスクリプトのURLを貼ります。
(ここいら辺の設定は他のやっていると思うので割愛)

これにより parse.認証済み設定のドメイン(例 parse.example.com)にくるメールはすべてDestination URLに設定されたURLにJSON形式で飛んできます。

boundメールを転送する設定

はじめは メールのreply_toとかに先程のparse.example.comを設定すれば飛んでくるかと思ったのですが、待てど暮らせど来ない。ドキュメントをよく読むとbounceメールの宛先である Return-PathはSendGridで生成しているので設定できないとのこと。

頓挫か?と思ったのですが、ちゃんと転送してくれる設定項目がありました。

Setting -> Mail Setting -> Forward Bounce Messagesをクリック

Specify email addressに先程のInbound Parseのメアドを設定します。
今回は parse@parse.example.comとします
(ドメインがあっていれば@の前は英単語だったら良いみたいです)
Forward Bounce Messages Statusを ENABLEDに

これでSendGrid側の準備は整いました。

boundメール受け取る準備(サーバ編)

今回は手持ちのサーバではphpとrubyが使えるのですが、railsに対応していないので phpからメールを書き出し、execでrubyスクリプトを呼び出すようにします。
ドキュメントを見るとメールの本体はEmailというプロパティのようです(実際はemailでしたが、、)

parse.php
$json = file_get_contents("php://input");

if(isset($json)){
    $content = json_decode($json,true);
    if(isset($content['email'])){
        $date = new DateTime();
        $filename = "mail_{$date->getTimestamp()}.txt";

        $fp = fopen($filename, "w");
        if($fp){
            if( flock($fp, LOCK_EX)){
                $status = fwrite($fp,$content['email']);
                if( $status ){
                    // ruby にわたす
                    exec("ruby parse.rb $filename",$output, $result);
                }else{
                    header('HTTP/1.1 403');
                }
            }
        }
    }
}
parse.rb
require("sisimai")

filename = ARGV[0]

# メールの解析
resultArray = Sisimai.make(filename)

if resultArray.is_a? Array 
    result = resultArray[0]
    resultFilename = File.basename(filename,".txt")+"_result.txt"
    File.open(resultFilename,"w") do |f|
        f.puts("result => " + result.reason)
    end
end

Sisimai.makeに入れればテキストでもfileでも解析してくれるところがrubyっぽいですね。

動かしてみる

メールを送信してます。

send.js
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)

const msg = {
  to: 'no-user@test-example.com',
  from: 'reply@example.com',
  subject: 'Sending with SendGrid is Fun',
  text: 'and easy to do anywhere, even with Node.js',
  html: '<strong>and easy to do anywhere, even with Node.js</strong>',
}
sgMail
  .send(msg)
  .then(() => {
    console.log('Email sent')
  })
  .catch((error) => {
    console.error(error)
  })

(実際には example.comはSendGridで認証したドメインです)
ここいら辺は SendGridのチュートリアルから抜粋

送信から結構タイムラグが発生しますが、メールは指定した parse@paser.example.comに転送され、Inbound Parseにて サーバに飛んできました。

mail_xxxx.txt
Received: by xxxxxx.sendgrid.net with SMTP id xxxxxxx Mon, 20 Dec 2021 15:45:06 +0000 (UTC)
Received: from xxxxxx.outbound-mail.sendgrid.net (unknown [50.31.45.1]) by mx0139p1mdw1.sendgrid.net (Postfix) with ESMTPS id 8CA314614E9 for <reply@parse.millonmillon.com>; Mon, 20 Dec 2021 15:45:06 +0000 (UTC)
MIME-Version: 1.0........
mail_xxxx_result.txt
result=>userunknown

SendGridはPostfixを使っているのですね。

まあ、最初にも書きましたが、docomoでの検証はできていませんが、思っていたことはできました。

感想

やっている途中でなんだかやってみたら回り道をしているような気がしましたが、一応解析まではできました。
さらっとやっているようですが、借りているレンタルサーバが bundlerが入っていなく、gemのインストールやrequireがうまく行かなかったり、apacheのエラーログが取れなかったりと結構時間を費やしてしまいました。

sisimai自体は結構良さそうなライブラリなので、Docomoの検証がうまいいったら何かしらに使いたいですね。(だれかちょっとだけでいいので使わせて、、)
次期アップデートではGoに移植されるそうです。

(いやNode.jsかRustに移植お願いします)

Discussion