LambdaでSisimaiを動かしてバウンスメールを解析するテスト

バウンスメール対策として、Sisimaiの利用を考えていました。ここで結論です。LambdaでSisimaiによるバウンスメール解析の仕組みが作れそうでした。LambdaでSisimaiを動かすような記事は見つからなかったのでアウトプットしてみることにしました。 libsisimai.org

"SisimaiはbounceHammerの後継となるバウンスメール(エラーメール)解析ライブラリ" です。

クックパッド開発者ブログにある以下の記事を興味深く読みました。 techlife.cookpad.com

読んだ結果、わりと壮大な仕組みなので、現在であればもう少し小さく仕組みを実現できるのではないかと思って試してみた記事です。できるだけマネージドな仕組みの上で実現したいのですが、バウンスメール解析のSaaSでピンとくるものはなさそうだったのでこの記事の内容を試しています。(できればコードは書きたくないのでマネージドバウンスメール解析SaaSほしい)

Sisimaiは標準入力、変数経由などいくつかの動かし方ができるのと、依存関係が少ないのでLambdaで動くのではないか?と思って試しました。デプロイのことは考えるのが面倒だったので最小の工数でやってみています。

手順

  • AWSのマネジメントコンソールでsisimai-ruby-testってfunctionを作っておきます。
  • 手元で以下のlambda_function.rbのコードを書きます

変数f の中身はsisimai作者のazumaさんが開発・デバッグ用に提供してくれているメールの中身をブチ込んでみています。検証用なのでいかにも雑です。(ここをS3 Eventの中身にすると、SES+S3で受信している仕組みで使えそうです。 https://github.com/sisimai/set-of-emails/blob/master/maildir/bsd/lhost-sendmail-32.eml

require 'json'
require 'sisimai'

def lambda_handler(event:, context:)
    f = 'Return-Path: <MAILER-DAEMON@nyaan.example.co.jp>
Received: from localhost (localhost)
    by nyaan.example.co.jp (V8/cf) id t91ECGic009238;
    Thu, 1 Oct 2015 23:12:16 +0900
Date: Thu, 1 Oct 2015 23:12:16 +0900
From: Mail Delivery Subsystem <poostmaster@example.co.jp>
Message-Id: <201510011412.t91ECGic009238@nyaan.example.co.jp>
To: <shironeko@example.co.jp>
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
    boundary="t91ECGic009238.1443708736/nyaan.example.co.jp"
Subject: Returned mail: see transcript for details
Auto-Submitted: auto-generated (failure)

This is a MIME-encapsulated message

--t91ECGic009238.1443708736/nyaan.example.co.jp

The original message was received at Thu, 1 Oct 2015 23:12:15 +0900
from c135.kyoto.example.ne.jp [192.0.2.78]

   ----- The following addresses had permanent fatal errors -----
<nyaaan@neko.example.jp>
    (reason: 550 5.1.1 Requested action not taken: mailbox unavailable)

   ----- Transcript of session follows -----
... while talking to inbound-smtp.us-west-2.amazonaws.com.:
>>> RCPT To:<nyaaan@neko.example.jp>
<<< 550 5.1.1 Requested action not taken: mailbox unavailable
550 5.1.1 <nyaaan@neko.example.jp>... User unknown
>>> DATA
<<< 503 Error: need RCPT command

--t91ECGic009238.1443708736/nyaan.example.co.jp
Content-Type: message/delivery-status

Reporting-MTA: dns; nyaan.example.co.jp
Received-From-MTA: DNS; c135.kyoto.example.ne.jp
Arrival-Date: Thu, 1 Oct 2015 23:12:15 +0900

Final-Recipient: RFC822; nyaaan@neko.example.jp
Action: failed
Status: 5.1.1
Remote-MTA: DNS; inbound-smtp.us-west-2.amazonaws.com
Diagnostic-Code: SMTP; 550 5.1.1 Requested action not taken: mailbox unavailable
Last-Attempt-Date: Thu, 1 Oct 2015 23:12:16 +0900

--t91ECGic009238.1443708736/nyaan.example.co.jp
Content-Type: message/rfc822

Return-Path: <shironeko@example.co.jp>
Received: from [192.0.2.43] (c135.kyoto.example.ne.jp [192.0.2.78])
    (authenticated bits=0)
    by nyaan.example.co.jp (V8/cf) with ESMTP id t91ECEic009234
    for <nyaaan@neko.example.jp>; Thu, 1 Oct 2015 23:12:15 +0900
X-SenderID: Sendmail Sender-ID Filter v1.0.0 nyaan.example.co.jp t91ECEic009234
Authentication-Results: nyaan.example.co.jp; sender-id=pass header.from=shironeko@example.co.jp; auth=pass (CRAM-MD5); spf=pass smtp.mfrom=shironeko@example.co.jp
From: "Shironeko, Nyaaan" <shironeko@example.co.jp>
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Subject: Nyaaaan
Message-Id: <4CB0AF09-E358-4D06-82B8-B8344B55CE02@example.co.jp>
Date: Thu, 1 Oct 2015 23:12:10 +0900
To: nyaaan@neko.example.jp
Mime-Version: 1.0 (Mac OS X Mail 7.3 \(1878.6\))
X-Mailer: Apple Mail (2.1878.6)

nyaaan

--t91ECGic009238.1443708736/nyaan.example.co.jp--

'
    v = Sisimai.make(f)
    v.each do |e|
      puts e.action
      puts e.deliverystatus
      puts e.reason
      puts e.diagnosticcode
      puts e.softbounce
    end
end
  • Gemfileを書きます
# frozen_string_literal: true

source "https://rubygems.org"

gem 'sisimai', '~> 4.25', '>= 4.25.7'
  • bundle installをします sisimaiが依存するojがNative extentionなのでAmazon Linux2上でビルドする必要があるのでこういう工夫が必要です。

docker run -v (pwd):/var/task -it lambci/lambda:build-ruby2.5 bundle install --path vendor/bundle

(かつての同僚が書いた記事に救われました。いい記事ですね。このチームでの楽しかったな~ Ruby on Lambdaで実現する、Eightの大規模画像処理基盤 - Sansan Builders Box

  • Zipをアップロードする形でLambdaにデプロイします

aws lambda update-function-code --function-name sisimai-ruby-test --zip-file fileb://function.zip

  • 空のeventを渡してLambdaをテスト実行します。下のJSONがSisimaiが返してくれた結果です。
[
   {
     "catch": "",
     "token": "b314cc8582c5f78c3381dbaf4b5e86d706d1c505",
     "lhost": "c135.kyoto.example.ne.jp",
     "rhost": "inbound-smtp.us-west-2.amazonaws.com",
     "alias": "",
     "listid": "",
     "reason": "userunknown",
     "action": "failed",
     "origin": "<MEMORY>",
     "subject": "Nyaaaan",
     "messageid": "4CB0AF09-E358-4D06-82B8-B8344B55CE02@example.co.jp",
     "replycode": "550",
     "smtpagent": "Sendmail",
     "softbounce": 0,
     "smtpcommand": "DATA",
     "destination": "neko.example.jp",
     "senderdomain": "example.co.jp",
     "feedbacktype": "",
     "diagnosticcode": "550 5.1.1 Requested action not taken: mailbox unavailable 503 Error: need RCPT command",
     "diagnostictype": "SMTP",
     "deliverystatus": "5.1.1",
     "timezoneoffset": "+0900",
     "addresser": "shironeko@example.co.jp",
     "recipient": "nyaaan@neko.example.jp",
     "timestamp": 1443708736
   }
 ]

とてもお手軽でしたね。SES+S3でメール受信して、S3 EventをトリガーにしてSisimaiが動いてバウンスメール解析するという仕組みを動かせそうな気持ちになってきました。よかったですね。