メールの解析、構造化されたデータやJSON文字列の取得など、 シシマイが使える環境にバウンスメールを用意すれば直ぐに解析結果を得られる 最も簡単で基本的な使い方を紹介します。
標準入力(STDIN)からバウンスメールのデータを読み込み、 シシマイで解析する方法の記述例です。 PerlやRubyのワンライナーでパイプラインを経由して 読み込む場合に便利な入力方法です。
変数からバウンスメールのデータを読み込み シシマイで解析する方法の記述例です。バウンスメール をデータベースやジョブキューシステムから読み出して 解析する場合に便利な方法です。
Sisimaiは既定の動作で配信できなかったメールだけを解析結果として 出力しますが、配信が成功したメール(配信通知)も解析結果に含める Sisimaiクラスのメソッド使用方法を紹介します。
Sisimaiは既定の動作で配信できなかったメールだけを解析結果として 出力しますが、不在応答(Vacation)で返ってきたメールも解析結果に含める Sisimaiクラスのメソッド使用方法を紹介します。
コールバック機能を使うとシシマイが解析する前のデータ に対して、特定ヘッダの取得や部分的な書き換えなど、 独自の処理を行い、その結果を解析データに含めることができます。
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版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で実装されました。
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版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 を指定することによって、配信が成功した事を通知するメールも解析結果に含めることができます。
配送が成功したメールは github.com/sisimai/set-of-emails リポジトリの rfc3464-28.eml がサンプルとして用意してあります。 ここでは、このメールを解析するサンプルコードを紹介します。
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
}
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
Sisimaiクラスの make()メソッドはSisimai 5.0.0で廃止になりました。 新たに実装されたrise()メソッドをかわりに使ってください。
Sisimai 5.0.0でバウンス理由が vacationとなる バウンスメールはデフォルトで解析されなくなりました。必要な場合は vacationパラメーターをrise()メソッドに指定してください。
Sisimaiクラスのrise()とdump()は第二引数にvacation を指定することによって、不在応答(Vacation)のメールも解析結果に含めることができます。
不在応答のメールは github.com/sisimai/set-of-emails リポジトリの rfc3834-01.eml がサンプルとして用意してあります。 ここでは、このメールを解析するサンプルコードを紹介します。
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
}
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::Messageのcatch() メソッドを通して、バウンスメールを独自に処理した結果を取得することができます。 この機能はSisimaiが解析結果に含めない元メッセージ全体や 元メッセージの特定のヘッダの値(例えば配信IDなど)を取り出したりするのに便利です。
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]);
messageで得られる文字列はバウンスメールの本文です。 バウンスメールそのもののヘッダーは含まれていませんので、ハッシュ化された headersを参照してください。
キー名 | データ型 | 説明 |
---|---|---|
headers | Hash | バウンスメールのヘッダ部分(ハッシュ化済み) |
message | String | ヘッダーは含まないバウンスメールのメール本文 |
mailで得られる文字列は、バウンスメールそのもの のヘッダーも含むバウンスメール全体です。
キー名 | データ型 | 説明 |
---|---|---|
kind | String | 元メールファイルの種類 |
*String | ヘッダーも含むバウンスメール全体 | |
path | String | 元メールファイルへのPATH |
fact | Array | 解析結果であるSisimai::Factのリスト |
rhost-google-03.eml にある以下の内容をコールバックで実行するコードによって取得し、元メールへの操作例を示します。
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 ...
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'});
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'])
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" } ]