シシマイでの解析方法

基本的な使い方

メールの解析、構造化されたデータやJSON文字列の取得など、 シシマイが使える環境にバウンスメールを用意すれば直ぐに解析結果を得られる 最も簡単で基本的な使い方を紹介します。

Read

標準入力を読む

標準入力(STDIN)からバウンスメールのデータを読み込み、 シシマイで解析する方法の記述例です。 PerlやRubyのワンライナーでパイプラインを経由して 読み込む場合に便利な入力方法です。

Read

変数から読む

変数からバウンスメールのデータを読み込み シシマイで解析する方法の記述例です。バウンスメール をデータベースやジョブキューシステムから読み出して 解析する場合に便利な方法です。

Read

配信成功も含む

Sisimaiは既定の動作で配信できなかったメールだけを解析結果として 出力しますが、配信が成功したメール(配信通知)も解析結果に含める Sisimaiクラスのメソッド使用方法を紹介します。

Read

不在応答も含む

Sisimaiは既定の動作で配信できなかったメールだけを解析結果として 出力しますが、不在応答(Vacation)で返ってきたメールも解析結果に含める Sisimaiクラスのメソッド使用方法を紹介します。

Read

コールバック

コールバック機能を使うとシシマイが解析する前のデータ に対して、特定ヘッダの取得や部分的な書き換えなど、 独自の処理を行い、その結果を解析データに含めることができます。

Read

標準入力から読む


Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。

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

Perl

STDINファイルハンドル

Sisimaiクラスのdump()rise()メソッドは、 第一引数にファイルハンドル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->rise(STDIN) }'
kijitora@example.jp
sironeko@example.org
                
Ruby

STDINオブジェクト

Ruby版Sisimaiも同様に、Sisimaiクラスのdump()rise()メソッドの 第一引数に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.rise(STDIN)'
[#<Sisimai::Data:0x007fa1b0993050 @addresser=#<Sisimai::Address:0x007fa1b0990e40 @user="kijitora", ...
                

変数から読み込む

Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。

Sisimaiクラスのrise()dump()の第一引数に変数 (Perlではスカラーリファレンス・RubyではStringオブジェクト)を指定することによって、 ファイルとしてのメールや標準入力以外からバウンスメールのデータを読み込むことができます。

バウンスメールをデータベースやジョブキューシステムから読み出す場合に有用なこの機能 (Sisimai::Mail::Memory)はSisimai 4.23.0で実装されました。

Perl

Scalar Reference

rise()dump()の第一引数にスカラーリファレンスを指定すると、 Sisimai内部ではSisimai::Mail::Memoryが呼び出され、メールのファイルや標準入力から読み込んだ のと同様、解析結果が得られます。

#! /usr/bin/env perl
use Sisimai;
my $f = 'From MAILER-DAEMON Thu Dec 22 17:54:04 2015...';   # バウンスメール全体
my $v = Sisimai->rise(\$f);
for my $e ( @$v ) {
    print $e->action;           # "delayed"
    print $e->deliverystatus;   # "4.7.0"
    print $e->reason;           # "expired"
    print $e->diagnosticcode;   # "Envelope expired"
    print $e->hardbounce;       # 0
}
                
Ruby

String Object

Ruby版Sisimaiではrise()dump()の第一引数に Stringオブジェクトを指定すると、Sisimai内部でSisimai::Mail::Memory が呼び出され、メールのファイルや標準入力から読み込んだのと同様、解析結果が得られます。

#! /usr/bin/env ruby
require 'sisimai'
f = 'From MAILER-DAEMON Thu Dec 22 17:54:04 2015...'    # バウンスメール全体
v = Sisimai.rise(f)
v.each do |e|
  puts e.action         # "delayed"
  puts e.deliverystatus # "4.7.0"
  puts e.reason         # "expired"
  puts e.diagnosticcode # "Envelope expired"
  puts e.hardbounce     # false
end
                

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

Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。

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

email

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

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

Perl

delivered => 1

rise()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->rise($f, 'delivered' => 1);
for my $e ( @$v ) {
    print $e->action;           # "delivered"
    print $e->deliverystatus;   # "2.1.5"
    print $e->reason;           # "delivered"
    print $e->diagnosticcode;   # "250 2.1.5 Ok"
    print $e->hardbounce;       # 0
}
                
Ruby

delivered: true

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

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

不在応答(Vacation)も解析結果に含める

Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。

Sisimai 5.0.0でバウンス理由が vacationとなる バウンスメールはデフォルトで解析されなくなりました。必要な場合は vacationパラメーターをrise()メソッドに指定してください。

Sisimaiクラスのrise()dump()は第二引数にvacation を指定することによって、不在応答(Vacation)のメールも解析結果に含めることができます。

email

不在応答(Vacation)のサンプルメール

不在応答のメールは github.com/sisimai/set-of-emails リポジトリの rfc3834-01.eml がサンプルとして用意してあります。 ここでは、このメールを解析するサンプルコードを紹介します。

Perl

vacation => 1

rise()dump()の第二引数にvacation=> 1を指定すると、 不在応答のメールも解析結果に含まれるようになります。vacation に偽となる値(0,undef,'')を指定した場合は、解析結果は生成されません。

#! /usr/bin/env perl
use Sisimai;
my $f = './set-of-emails/maildir/bsd/rfc3834-01.eml';
my $v = Sisimai->rise($f, 'vacation' => 1);
for my $e ( @$v ) {
    print $e->action;           # ""
    print $e->deliverystatus;   # ""
    print $e->reason;           # "vacation"
    print $e->diagnosticcode;   # "I am currently away returning to the office on May 5th."
    print $e->hardbounce;       # 0
}
                
Ruby

vacation: true

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

#! /usr/bin/env ruby
require 'sisimai'
f = './set-of-emails/maildir/bsd/rfc3834-01.eml'
v = Sisimai.rise(f, vacation: true)
v.each do |e|
  puts e.action         # ""
  puts e.deliverystatus # ""
  puts e.reason         # "vacation"
  puts e.diagnosticcode # "I am currently away returning to the office on May 5th."
  puts e.hardbounce     # false
end
                

コールバック機能

Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。

Sisimai 5.0.0でコールバック機能の仕様(引数の数・型)が変りました。 非互換の変更につき、Version 4でのコールバック機能は4-stable ブランチの GitHub/Callback Feature を確認してください。

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

c___

c___に渡す引数

Sisimaiクラスのrise()メソッドにおけるc___ (c_が三つ、釣り針に見える) に渡せる引数は、Perlのフックメソッド(サブルーチン)には配列リファレンス1つ、 RubyのフックメソッドにはArrayオブジェクト1つ です。引数の構造は以下のようになっています。

c___に渡す配列のインデックス 処理対象
[0] (下記コード例にある$code0) メールのヘッダーheadersおよび本文 message
[1] (下記コード例にある$code1) 元メールのファイルに対して
my $code0 = sub {
    my $args = shift;
    my $head = $args->{'headers'}; # ヘッダー
    my $body = $args->{'message'}; # 本文
};
my $code1 = sub {
    my $args = shift;
    my $kind = $args->{'kind'}; # (String)  Sisimai::Mail->kind
    my $mail = $args->{'mail'}; # (*String) Entire email message
    my $path = $args->{'path'}; # (String)  Sisimai::Mail->path
    my $fact = $args->{'fact'}; # (*Array)  List of Sisimai::Fact
};
my $list = Sisimai->rise('/path/to/mbox', 'c___' => [$code0, $code1]);
                
c___[0]

c___[0]の入力値

messageで得られる文字列はバウンスメールの本文です。 バウンスメールそのもののヘッダーは含まれていませんので、ハッシュ化された headersを参照してください。

キー名 データ型 説明
headers Hash バウンスメールのヘッダ部分(ハッシュ化済み)
message String ヘッダーは含まないバウンスメールのメール本文
c___[1]

c___[1]の入力値

mailで得られる文字列は、バウンスメールそのもの のヘッダーも含むバウンスメール全体です。

キー名 データ型 説明
kind String 元メールファイルの種類
mail *String ヘッダーも含むバウンスメール全体
path String 元メールファイルへのPATH
fact Array 解析結果であるSisimai::Factのリスト
email

電子メール: rhost-google-03.eml

rhost-google-03.eml にある以下の内容をコールバックで実行するコードによって取得し、元メールへの操作例を示します。

  • バウンスメールのヘッダにあるReturn-Pathの値を取得
  • バウンスメールのヘッダにあるAuthentication-Resultsの値を取得
  • 本文のX-Postfix-Queue-IDの値を取得
  • 元メールに独自ヘッダーを書き加えて別のPATHへ保存する
Return-Path: <>
Received: from mail4.example.com (mx4.example.com [192.0.2.24])
	by secure.example.jp (Postfix) with ESMTPS id 4SnNbh0r3mz24sXs
	for ; Thu,  9 Apr 2022 23:34:45 +0900 (JST)
Received: by mail4.example.com (Postfix)
	id t8WLKlmm99zqL71C; Thu,  9 Apr 2022 23:34:45 +0900 (JST)
Authentication-Results: secure.example.jp; spf=fail smtp.helo=mail4.example.com
Date: Thu,  9 Apr 2022 23:34:45 +0900 (JST)
From: MAILER-DAEMON@mail4.example.com (Mail Delivery System)
Subject: Undelivered Mail Returned to Sender
To: nginx@mail4.example.com
...
 (expanded from ): host
    gmail-smtp-in.l.google.com[64.233.188.26] said: 550-5.7.26 The MAIL FROM
    domain [mail4.example.com] has an SPF record with a 550-5.7.26 hard fail
    policy (-all) but it fails to pass SPF checks with the ip: 550-5.7.26
    [192.0.2.24]. To best protect our users from spam and phishing,
    550-5.7.26 the message has been blocked. For instructions on setting up
    550-5.7.26 authentication, go to 550 5.7.26
    https://support.google.com/mail/answer/81126#authentication
    2-222222630d46222222b002b2d2neko2cat2222222cat.22 - gsmtp (in reply to end
    of DATA command)

--Lms5yfCw0Vz5CRp3.1647426971/mail4.example.com
Content-Description: Delivery report
Content-Type: message/delivery-status

Reporting-MTA: dns; mail4.example.com
X-Postfix-Queue-ID: Lms5yfCw0Vz5CRp3
X-Postfix-Sender: rfc822; nginx@mail4.example.com
Arrival-Date: Thu,  9 Apr 2022 23:34:45 +0900 (JST)

Final-Recipient: rfc822; kijitora@google.example.com
Original-Recipient: rfc822;neko@example.co.jp
Action: failed
Status: 5.7.26
Remote-MTA: dns; gmail-smtp-in.l.google.com
Diagnostic-Code: smtp; 550-5.7.26 The MAIL FROM domain [mail4.example.com] has
...
                
Perl

Sisimai->rise($f, 'c___' => [$c0, $c1])

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

#! /usr/bin/env perl
use Sisimai;
my $fn = 'set-of-emails/maildir/bsd/rhost-google-03.eml';
my $c0 = sub {
    my $args = shift;
    my $data = { 'queue-id' => '', 'authentication-results' => '', 'return-path' => '' };

    $data->{'return-path'} = $args->{'headers'}->{'return-path'} || '';
    $data->{'authentication-results'} = $args->{'headers'}->{'authentication-results'} || '';
    if( $args->{'message'} =~ m/^X-Postfix-Queue-ID:\s*(.+)$/m ) {
        $data->{'queue-id'} = $1;
    }
    return $data;
};
my $c1 = sub {
    my $args = shift;
    my $kind = $args->{'kind'}; # (String)  Sisimai::Mail->kind
    my $mail = $args->{'mail'}; # (*String) Entire email message
    my $path = $args->{'path'}; # (String)  Sisimai::Mail->path
    my $fact = $args->{'fact'}; # (*Array)  List of Sisimai::Fact

    for my $e ( @$fact ) {
        # "catch"アクセサの中に独自の情報を保存する
        $e->{'catch'} ||= {};
        $e->{'catch'}->{'size'} = length $$mail;
        $e->{'catch'}->{'kind'} = ucfirst $kind;

        # "X-Sisimai-Parsed:"ヘッダーを追加して別のPATHに元メールを保存する
        my $a = sprintf("X-Sisimai-Parsed: %d\n", scalar @$fact);
        my $f = IO::File->new(sprintf("/tmp/sisimai-%s.eml", $e->token), 'w');
        my $v = $$mail; $v =~ s/^(From:.+)$/$a$1/m;
        print $f $v; $f->close;
    }

    # 解析が終わったらMaildir/にあるファイルを削除する
    unlink $path if $kind eq 'maildir';
};

my $js = Sisimai->dump($fn, 'c___' => [$c0, $c1]); # JSON文字列を得る
my $rv = Sisimai->rise($fn, 'c___' => [$c0, $c1]);
printf("Authentication-Results: %s\n", $rv->[0]->{'catch'}->{'authentication-results'});
printf("X-Postfix-Queue-ID: %s\n", $rv->[0]->{'catch'}->{'queue-id'});
printf("Return-Path: %s\n", $rv->[0]->{'catch'}->{'return-path'});
                
Ruby

Sisimai.rise(f, c___: [c0, c1])

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

#! /usr/bin/env ruby
require 'sisimai'
fn = 'set-of-emails/maildir/bsd/rhost-google-03.eml'
c0 = lambda do |args|
  data = { 'queue-id' => '', 'authentication-results' => '', 'return-path' => '' }
  data['return-path'] = args['headers']['return-path'] || ''
  data['authentication-results'] = args['headers']['authentication-results'] || ''
  if cv = args['message'].match(/^X-Postfix-Queue-ID:\s*(.+)$/)
    data['queue-id'] = cv[1]
  end
  return data
end

c1 = lambda do |args|
  kind = args['kind'] # (String)  Sisimai::Mail.kind
  mail = args['mail'] # (*String) Entire email message
  path = args['path'] # (String)  Sisimai::Mail.path
  fact = args['fact'] # (*Array)  List of Sisimai::Fact

  fact.each do |e|
    # "catch"アクセサの中に独自の情報を保存する
    e.catch ||= {}
    e.catch['size'] = mail.size
    e.catch['kind'] = kind

    # "X-Sisimai-Parsed:"ヘッダーを追加して別のPATHに元メールを保存する
    a = sprintf("X-Sisimai-Parsed: %d", fact.size)
    b = mail.sub(/^(To:.+$)/,'\1'+ "\n" + a)
    File.open(sprintf("/tmp/sisimai-%s.eml", e.token), "w") do |f|
      f.puts(b)
    end

    # 解析が終わったらMaildir/にあるファイルを削除する
    File.delete(path) if kind == 'maildir'
  end
end

js = Sisimai.dump(fn, c___: [c0, c1]) # JSON文字列を得る
rv = Sisimai.rise(fn, c___: [c0, c1])
printf("Authentication-Results: %s\n", rv[0].catch['authentication-results'])
printf("X-Postfix-Queue-ID: %s\n", rv[0].catch['queue-id'])
printf("Return-Path: %s\n", rv[0].catch['return-path'])
                
JSON

dump()で出力したJSON

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

[
  {
    "addresser": "kijitora@google.example.com",
    "messageid": "vDcsF1hr4zzywp69@mail4.example.com",
    "recipient": "kijitora@google.example.com",
    "diagnosticcode": "host gmail-smtp-in.l.google.com[64.233.188.26] said: The MAIL FROM domain [mail4.example.com] has an SPF record with a hard fail policy (-all) but it fails to pass SPF checks with the ip: [192.0.2.24]. To best protect our users from spam and phishing, the message has been blocked. For instructions on setting up authentication, go to https://support.google.com/mail/answer/81126#authentication 2-222222630d46222222b002b2d2neko2cat2222222cat.22 - gsmtp (in reply to end of DATA command)",
    "smtpagent": "Postfix",
    "destination": "google.example.com",
    "alias": "neko@example.co.jp",
    "reason": "authfailure",
    "lhost": "gmail-smtp-in.l.google.com",
    "senderdomain": "google.example.com",
    "token": "fe75b94dd0489b106fa041c460064dd6afa8196b",
    "subject": "Nyaan",
    "deliverystatus": "5.7.26",
    "timezoneoffset": "+0900",
    "listid": "",
    "origin": "set-of-emails/maildir/bsd/rhost-google-03.eml",
    "action": "failed",
    "timestamp": 1649514885,
    "hardbounce": 0,
    "rhost": "gmail-smtp-in.l.google.com",
    "catch": {
      "size": 4436,
      "kind": "Mailbox",
      "authentication-results": "secure.example.jp; spf=fail smtp.helo=mail4.example.com",
      "return-path": "<>",
      "queue-id": "Lms5yfCw0Vz5CRp3"
    },
    "diagnostictype": "SMTP",
    "replycode": 550,
    "feedbacktype": "",
    "smtpcommand": "DATA"
  }
]