シシマイでの解析方法

基本的な使い方

メールの解析、構造化されたデータやJSON文字列の取得化など、 簡単な使い方を紹介します。

Read

標準入力を読む

標準入力(STDIN)からバウンスメールのデータを読み込み シシマイで解析する方法の記述例です。

Read

配信成功も含む

配信が成功したメール(配信通知)も解析結果に含める Sisimaiクラスのメソッド使用方法を紹介します。

Read

メール以外

メール以外のデータソースでは、メール配信クラウドの バウンスオブジェクトも解析することができます。

Read

コールバック

コールバック機能を使うとシシマイが解析する前のデータ に対して、独自の処理を行うことができます。

Read

標準入力から読む


Sisimai(シシマイ)のmake()dump()メソッドは、 引数にファイル名を指定する以外に、STDINを指定することによって、 標準入力(STDIN)からバウンスメールのデータを読み込むことができます。 この機能は初期のSisimaiから実装されていましたが、バグがあったため、 読み込みができない場合がありました。このバグはSisimai 4.18.1 (Perl版) (Ruby版) にて修正済みです。

Perl

STDINファイルハンドル

Sisimaiクラスのdump()make()メソッドは、 第一引数にファイルハンドルSTDINを指定すると、 標準入力からバウンスメールのデータを読み込み、解析結果を返します。

% cat ./path/to/email | perl -MSisimai -lE 'print Sisimai->dump(STDIN)' | jq
[
  {
    "listid": "",
    "smtpagent": "Sendmail",
    ...
                
% cat ./path/to/email | perl -MSisimai -lE 'print $_->recipient->address for @{ Sisimai->make(STDIN) }'
kijitora@example.jp
sironeko@example.org
                
Ruby

STDINオブジェクト

Ruby版Sisimaiも同様に、Sisimaiクラスのdump()make()メソッドの 第一引数にSTDINオブジェクトを指定すると、 標準入力からバウンスメールのデータを読み込み、解析結果を返します。

% cat ./path/to/email | ruby -Ilib -rsisimai -e 'puts Sisimai.dump(STDIN)' | jq
[
  {
    "catch": "",
    "token": "d295b517a3ad6f8748031e1068e07ebd089c0277",
    ...
                
% cat ./path/to/email | ruby -rsisimai -e 'p Sisimai.make(STDIN)'
[#<Sisimai::Data:0x007fa1b0993050 @addresser=#<Sisimai::Address:0x007fa1b0990e40 @user="kijitora", ...
                

配信成功も解析結果に含める

Sisimaiのmake()dump()は第二引数にdelivered を指定することによって、配信が成功した事を通知するメールも解析結果に含めることができます。

email

配信成功のサンプルメール

配送が成功したメールは github.com/sisimai/set-of-emails リポジトリの rfc3464-28.eml がサンプルとして用意してあります。 ここでは、このメールを解析するサンプルコードを紹介します。

Perl

delivered => 1

make()dump()の第二引数にdelivered => 1を指定すると、 配信に成功したメール(配信通知)も解析結果に含まれるようになります。delivered に偽となる値(0,undef,'')を指定した場合は、解析結果は生成されません。

#! /usr/bin/env perl
use Sisimai;
my $f = './set-of-emails/maildir/bsd/rfc3464-28.eml';
my $v = Sisimai->make($f, 'delivered' => 1);
for my $e ( @$v ) {
    print $e->action;           # deliverable
    print $e->deliverystatus;   # 2.1.5
    print $e->reason;           # delivered
    print $e->diagnosticcode;   # 250 2.1.5 Ok
    print $e->softbounce;       # -1
}
                
Ruby

delivered: true

Perl版と同様に、Ruby版Sisimaiでもmake()dump()の第二引数に delivered: trueを指定すると、配信に成功したメール(配信通知)も 解析結果に含まれるようになります。delivered に偽となる値(nil,false)を指定した場合は、解析結果は生成されません。

#! /usr/bin/env ruby
require 'sisimai'
f = './set-of-emails/maildir/bsd/rfc3464-28.eml'
v = Sisimai.make(f, delivered: true)
v.each do |e|
  puts e.action         # deliverable
  puts e.deliverystatus # 2.1.5
  puts e.reason         # delivered
  puts e.diagnosticcode # 250 2.1.5 Ok
  puts e.softbounce     # -1
end
                

メール以外を解析する

Sisimaiのmake()dump()は第二引数にinput を指定することによって、メール以外のバウンス記録を解析することができます。 この機能はSisimai 4.20.0で実装されました。現時点で解析できるものは SNSで取得できるAmazon SESのバウンスオブジェクト と、 用意されているWeb APIで取得できるSendGridのバウンスオブジェクト のみです。

JSON

バウンスオブジェクト(JSON)

メール配信クラウド(Amazon SESとSendGrid)から取得したバウンスオブジェクトのJSONファイルは github.com/sisimai/set-of-emails リポジトリのjsonapi/ディレクトリに何種類かを用意してあります。 ここでは、 ced-us-sendgrid-03.json (SendGridのバウンスオブジェクト)を解析するコードを紹介します。

Perl

input => 'json'

make()dump()の第一引数にデコードしたJSON(バウンスオブジェクト)を、 第二引数にinput => 'json'をそれぞれ指定すると、バウンスオブジェクトを解析して Sisimaiの形式として解析結果を得られます。

#! /usr/bin/env perl
use Sisimai;
use JSON;
my $q = '[{"status": "5.1.1", "created": "2011-10-08 14:05:44", "reason": "550...';
my $j = JSON->new;
my $v = Sisimai->make($j->decode($q), 'input' => 'json');
for my $e ( @$v ) {
    print $e->smtpagent;        # CED::US::SendGrid
    print $e->deliverystatus;   # 5.1.1
    print $e->reason;           # userunknown
    print $e->diagnosticcode;   # 550 5.1.1 The email account that you tried to...
    print $e->softbounce;       # 0
}
                
Ruby

input: 'json'

Perl版と同様に、Ruby版Sisimaiでもmake()dump()の第一引数に JSONオブジェクト(バウンスオブジェクトのJSONをデコードしたもの)を、 第二引数にinput: 'json'を指定すると、JSON形式のバウンスオブジェクトも 解析できます。

#! /usr/bin/env ruby
require 'sisimai'
require 'json'
q = '[{"status": "5.1.1", "created": "2011-10-08 14:05:44", "reason": "550...'
v = Sisimai.make(JSON.load(q), input: 'json')
v.each do |e|
  puts e.smtpagent      # CED::US::SendGrid
  puts e.deliverystatus # 5.1.1
  puts e.reason         # userunknown
  puts e.diagnosticcode # 550 5.1.1 The email account that you tried to...
  puts e.softbounce     # 0
end
                

コールバック機能

Sisimai 4.19.0から実装されたコールバック機能を使用すると、 Sisimai::DataまたはSisimai::Messagecatch() メソッドを通して、バウンスメールを独自に処理した結果を取得することができます。 この機能はSisimaiが解析結果に含めない元メッセージ全体や 元メッセージの特定のヘッダの値(例えば配信IDなど)を取り出したりするのに便利です。

argv

呼び出されるメソッドの引数

Perlのフックメソッド(サブルーチン)にはハッシュリファレンス1つ、 RubyのフックメソッドにはHashオブジェクト1つ が引数として渡されます。引数の構造は以下のようになっています。

キー名 データ型 説明 メール JSON
datasrc String 解析対象データの種類 ○ (email) ○ (json)
headers Hash バウンスメールのヘッダ部分(ハッシュ化済み) undef,nil
message String バウンスメールのメール本文(改行・元メッセージを含む) undef,nil
bounces Hash or Array バウンスオブジェクト(Decoded JSON) undef,nil
email

電子メール: mta-postfix-16.eml

mta-postfix-16.eml にある以下の内容をコールバックで実行するコードによって取得する例を示します。

  • ヘッダのReceived-SPFの値
  • 本文のX-Postfix-Queue-IDの値
Return-Path: <>
Received: by 192.0.2.22 with SMTP id fffffff000;
    Thu, 20 Jan 2011 23:34:45 +0900 (JST)
Received: from mx0.example.jp (mx0.example.jp [192.0.2.9])
    by mx.example.org with ESMTP id 0000000000000000000000;
    Thu, 20 Jan 2011 23:34:45 +0900 (JST)
Received-SPF: pass (example.org: best guess record for domain of mx0.example.jp designates 192.0.2.9 as permitted sender) client-ip=192.0.2.9;
Received: by mx0.example.jp (Postfix)
    id FFFFFFFFFFFF; Thu, 20 Jan 2011 23:34:45 +0900 (JST)
Date: Thu, 20 Jan 2011 23:34:45 +0900 (JST)
From: MAILER-DAEMON@example.jp (Mail Delivery System)
Subject: Undelivered Mail Returned to Sender
To: shironeko@cat.example.com
Auto-Submitted: auto-replied
MIME-Version: 1.0
Content-Type: multipart/report; report-type=delivery-status;
    boundary="FFFFFFFFFF.00000000/mx0.example.jp"
Content-Transfer-Encoding: 8bit
Message-Id: <000000000000000.FFFFFFFFFFFF@mx0.example.jp>

This is a MIME-encapsulated message.

--FFFFFFFFFF.00000000/mx0.example.jp
Content-Description: Notification
Content-Type: text/plain; charset=us-ascii

This is the mail system at host mx0.example.jp.

I'm sorry to have to inform you that your message could not
be delivered to one or more recipients. It's attached below.

For further assistance, please send mail to <postmaster>

If you do so, please include this problem report. You can
delete your own text from the attached returned message.

                   The mail system

<kijitora@example.go.jp>: host mx1.example.go.jp[192.0.2.127] said: 550 5.1.6 recipient
    no longer on server: kijitora@example.go.jp (in reply to RCPT TO command)

--FFFFFFFFFF.00000000/mx0.example.jp
Content-Description: Delivery report
Content-Type: message/delivery-status

Reporting-MTA: dns; mx0.example.jp
X-Postfix-Queue-ID: FFFFFF000000
X-Postfix-Sender: rfc822; shironeko@cat.example.com
...
                

フックメソッドには一つの引数が以下のように渡されます。 mta-postfix-16.eml のヘッダ部分(ハッシュ化済み)とメッセージ部分(文字列)を構造化したものです。 undefになっている部分は、Rubyではnilと読み替えてください。

{
  'bounces' => undef,
  'datasrc' => 'email',
  'headers' => {
     'x-mailer' => undef,
     'return-path' => '<>',
     'content-type' => 'multipart/report; report-type=delivery-status; boundary="FFFFFFFFFF.00000000/mx0.example.jp"',
     'subject' => 'Undelivered Mail Returned to Sender',
     'date' => 'Thu, 20 Jan 2011 23:34:45 +0900 (JST)',
     'to' => 'shironeko@cat.example.com',
     'received-spf' => 'pass (example.org: best guess record for domain of mx0.example.jp designates 192.0.2.9 as permitted sender) client-ip=192.0.2.9;',
     'reply-to' => undef,
     'message-id' => '<000000000000000.FFFFFFFFFFFF@mx0.example.jp>',
     'from' => 'MAILER-DAEMON@example.jp (Mail Delivery System)',
     'content-transfer-encoding' => '8bit',
     'received' => [
         'by 192.0.2.22 with SMTP id fffffff000; Thu, 20 Jan 2011 23:34:45 +0900 (JST)',
         'from mx0.example.jp (mx0.example.jp [192.0.2.9]) by mx.example.org with ESMTP id 0000000000000000000000; Thu, 20 Jan 2011 23:34:45 +0900 (JST)',
         'by mx0.example.jp (Postfix) id FFFFFFFFFFFF; Thu, 20 Jan 2011 23:34:45 +0900 (JST)'
       ],
     'auto-submitted' => 'auto-replied'
     },
  'message' => 'This is a MIME-encapsulated message.
...
Reporting-MTA: dns; mx0.example.jp
X-Postfix-Queue-ID: FFFFFF000000
X-Postfix-Sender: rfc822; shironeko@cat.example.com
Arrival-Date: Thu, 20 Jan 2011 23:34:45 +0900 (JST)

Final-Recipient: rfc822; kijitora@example.go.jp
Action: failed
Status: 5.1.6
...
Subject: Nyaaaaan
To: Kijitora 
Message-Id: <0000000000000.FFFFFF000000@mx0.example.jp>

Nyaaaan
'
  }
                
Perl

Sisimai->make($f, 'hook' => $x, 'field' => [])

Sisimaiクラスのmake()dump()メソッドに、 実行したい処理内容を書いたサブルーチンのコードリファレンスを第二引数 hookとして渡すことができます。

また、ヘッダ部分から抽出したいヘッダがある場合は、ヘッダ名を引数field に配列リファレンスで渡す必要があります。 このfield引数はSisimai 4.20.2で実装されました。

#! /usr/bin/env perl
use Sisimai;
my $f = './set-of-emails/maildir/bsd/mta-postfix-16.eml';
my $x = sub {
    my $argv = shift;
    my $data = { 'queue-id' => '', 'received-spf' => '' };

    $data->{'received-spf'} = $argv->{'headers'}->{'received-spf'} || '';
    if( $argv->{'message'} =~ m/^X-Postfix-Queue-ID:\s*(.+)$/m ) {
        $data->{'queue-id'} = $1;
    }
    return $data;
};
my $h = ['Received-SPF'];
my $j = Sisimai->dump($f, 'hook' => $x, 'field' => $h); # JSON文字列を得る
my $v = Sisimai->make($f, 'hook' => $x, 'field' => $h);
print $v->[0]->{'catch'}->{'received-spf'};  # pass (example.org:...
print $v->[0]->{'catch'}->{'queue-id'};      # FFFFFF000000
                
Ruby

Sisimai.make(f, hook: x, field: [])

Sisimaiクラスのmake()dump()メソッドに、 実行したい処理内容を書いたProcオブジェクトをhook: として渡すことができます。

また、ヘッダ部分から抽出したいヘッダがある場合は、ヘッダ名を引数field: に配列で渡す必要があります。 このfield:引数はSisimai 4.20.2で実装されました。

#! /usr/bin/env ruby
require 'sisimai'
f = './set-of-emails/maildir/bsd/mta-postfix-16.eml'
x = lambda do |argv|
  data = { 'queue-id' => '', 'received-spf' => '' }

  data['received-spf'] = argv['headers']['received-spf'] || ''
  if cv = argv['message'].match(/^X-Postfix-Queue-ID:\s*(.+)$/)
    data['queue-id'] = cv[1]
  end
  return data
end

h = ['Received-SPF']
j = Sisimai.dump(f, hook: x, field: h)    # JSON文字列を得る
v = Sisimai.make(f, hook: x, field: h)
puts v[0]['catch']['received-spf'] # pass (example.org: ...
puts v[0]['catch']['queue-id']     # FFFFFF000000
                
JSON

dump()で出力したJSON

Sisimaiのdump()メソッドにフックメソッドを渡して得たJSON文字列は 次のような内容になります。

[
  {
    "timestamp": 1270043983,
    "smtpcommand": "RCPT",
    "subject": "Nyaaan",
    "recipient": "kijitora@example.jp",
    "destination": "example.jp",
    "senderdomain": "example.co.jp",
    "token": "bb2a5dfb6161f396300b24acd9bf637c1c45a58a",
    "smtpagent": "qmail",
    "replycode": "450",
    "softbounce": 1,
    "catch": {
      "received-spf": "pass (example.org: best guess record for domain of mx0.example.jp designates 192.0.2.9 as permitted sender) client-ip=192.0.2.9;",
      "queue-id": "FFFFFF000000",
    },
    "rhost": "192.0.2.135",
    "timezoneoffset": "+0900",
    "addresser": "shironeko@example.co.jp",
    "diagnostictype": "SMTP",
    "action": "failed",
    "listid": "",
    "feedbacktype": "",
    "diagnosticcode": "192.0.2.135 does not like recipient. Remote host said: 450 4.2.2 ... Mailbox Full Giving up on 192.0.2.135. I'm not going to try again; this message has been in the queue too long.",
    "reason": "mailboxfull",
    "deliverystatus": "4.2.2",
    "alias": "",
    "lhost": "",
    "messageid": "57D03121-12F7-4801-B30F-9E44B15E56DC@example.co.jp"
  }
]
                
Ojbect

バウンスオブジェクト: ced-us-amazonses-01.eml

ced-us-amazonses-01.json 内、SNSで取得できるバウンスオブジェクトからmail.sendingAccountIdの値を、 コールバックで実行するコードで取得するサンプルコードで説明します。

{
  "notificationType": "Bounce",
  "bounce": {
    "bounceType": "Permanent",
    "bounceSubType": "General",
    "bouncedRecipients": [
      {
        "emailAddress": "bounce@simulator.amazonses.com",
        "action": "failed",
        "status": "5.1.1",
        "diagnosticCode": "smtp; 550 5.1.1 user unknown"
      }
    ],
    "timestamp": "2016-10-21T00:06:40.502Z",
    "feedbackId": "01010157e48fa03f-c7e948fe-3c34-403e-b681-02a497797067-000000",
    "reportingMTA": "dsn; a27-23.smtp-out.us-west-2.amazonses.com"
  },
  "mail": {
    "timestamp": "2016-10-21T00:06:39.000Z",
    "source": "kijitora@neko.example.org",
    "sourceArn": "arn:aws:ses:us-west-2:123456789012:identity/kijitora@neko.example.org",
    "sendingAccountId": "123456789012",
    ...
                

フックメソッドには一つの引数が以下のように渡されます。 ced-us-amazonses-01.json のバウンスオブジェクト(JSON)をデコードして構造化したものです。 undefになっている部分は、Rubyではnilと読み替えてください。

{
    'datasrc' => 'json',
    'headers' => undef,
    'message' => undef,
    'bounces' => {
        'bounce' => {
            'feedbackId' => '01010157e48fa03f-c7e948fe-3c34-403e-b681-02a497797067-000000',
            'timestamp' => '2016-10-21T00:06:40.502Z',
            'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
            'bounceType' => 'Permanent',
        },
        'mail' => {
            'source' => 'kijitora@neko.example.org',
            'sendingAccountId' => '123456789012',
            'destination' => [
                'bounce@simulator.amazonses.com'
            ],
            'timestamp' => '2016-10-21T00:06:39.000Z',
            'sourceArn' => 'arn:aws:ses:us-west-2:123456789012:identity/kijitora@neko.example.org',
            'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
        },
        'notificationType' => 'Bounce'
    }
}
                
Perl

Sisimai->make($f, 'hook' => $x, 'input' => 'json')

Sisimaiクラスのmake()dump()メソッドに、 実行したい処理内容を書いたサブルーチンのコードリファレンスを第二引数 hookとして渡すことができます。

#! /usr/bin/env perl
use Sisimai;
use JSON;
my $q = '{"notificationType": "Bounce", "bounce": {...';
my $o = JSON->new;
my $x = sub {
    my $argv = shift;
    my $data = { 'account-id' => '' };

    $data->{'account-id'} = $argv->{'bounces'}->{'mail'}->{'sendingAccountId'};
    return $data;
};
my $j = Sisimai->dump($o->decode($q), 'hook' => $x, 'input' => 'json'); # JSON文字列を得る
my $v = Sisimai->make($o->decode($q), 'hook' => $x, 'input' => 'json');
print $v->[0]->{'catch'}->{'account-id'};     # 123456789012
                
Ruby

Sisimai.make(f, hook: x, input: 'json')

Sisimaiクラスのmake()dump()メソッドに、 実行したい処理内容を書いたProcオブジェクトを hook:として渡すことができます。

#! /usr/bin/env ruby
require 'sisimai'
require 'json'
q = '{"notificationType": "Bounce", "bounce": {...'
o = JSON.load(q)
x = lambda do |argv|
  data = { 'acount-id' => nil }

  data['account-id'] = argv['bounces']['mail']['sendingAccountId']
  return data
end

j = Sisimai.dump(o, hook: x, input: 'json')    # JSON文字列を得る
v = Sisimai.make(o, hook: x, input: 'json')
puts v[0]['catch']['account-id']  # 123456789012
                
JSON

dump()で出力したJSON

Sisimaiのdump()メソッドにフックメソッドを渡して得たJSON文字列は 次のような内容になります。

[
  {
    "timezoneoffset": "+0900",
    "reason": "userunknown",
    "replycode": "550",
    "messageid": "01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000",
    "lhost": "a27-23.smtp-out.us-west-2.amazonses.com",
    "destination": "simulator.amazonses.com",
    "senderdomain": "neko.example.org",
    "diagnosticcode": "550 5.1.1 user unknown",
    "addresser": "kijitora@neko.example.org",
    "catch": {
      "account-id": 123456789012
    },
    "subject": "nyaan",
    "deliverystatus": "5.1.1",
    "token": "79f14902d1e4b8f9c40727609d7a752a1700564e",
    "feedbacktype": "",
    "softbounce": 0,
    "rhost": "",
    "smtpcommand": "",
    "smtpagent": "CED::US::AmazonSES",
    "recipient": "bounce@simulator.amazonses.com",
    "alias": "",
    "diagnostictype": "SMTP",
    "listid": "",
    "action": "failed",
    "timestamp": 1476976000
  }
]