-
Notifications
You must be signed in to change notification settings - Fork 26
/
Data.pm
756 lines (580 loc) · 28.5 KB
/
Data.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
package Sisimai::Data;
use feature ':5.10';
use strict;
use warnings;
use Sisimai::Address;
use Sisimai::String;
use Sisimai::Reason;
use Sisimai::Rhost;
use Sisimai::Time;
use Sisimai::DateTime;
use Sisimai::SMTP::Error;
use Class::Accessor::Lite (
'new' => 0,
'rw' => [
'catch', # [?] Results generated by hook method
'token', # [String] Message token/MD5 Hex digest value
'lhost', # [String] local host name/Local MTA
'rhost', # [String] Remote host name/Remote MTA
'alias', # [String] Alias of the recipient address
'listid', # [String] List-Id header of each ML
'reason', # [String] Bounce reason
'action', # [String] The value of Action: header
'origin', # [String] Email path as a data source
'subject', # [String] UTF-8 Subject text
'timestamp', # [Sisimai::Time] Date: header in the original message
'addresser', # [Sisimai::Address] From address
'recipient', # [Sisimai::Address] Recipient address which bounced
'messageid', # [String] Message-Id: header
'replycode', # [String] SMTP Reply Code
'smtpagent', # [String] Module(Engine) name
'softbounce', # [Integer] 1 = Soft bounce, 0 = Hard bounce, -1 = ?
'smtpcommand', # [String] The last SMTP command
'destination', # [String] The domain part of the "recipinet"
'senderdomain', # [String] The domain part of the "addresser"
'feedbacktype', # [String] Feedback Type
'diagnosticcode', # [String] Diagnostic-Code: Header
'diagnostictype', # [String] The 1st part of Diagnostic-Code: Header
'deliverystatus', # [String] Delivery Status(DSN)
'timezoneoffset', # [Integer] Time zone offset(seconds)
]
);
state $RetryIndex = Sisimai::Reason->retry;
state $RFC822Head = Sisimai::RFC5322->HEADERFIELDS('all');
state $AddrHeader = {
'addresser' => $RFC822Head->{'addresser'},
'recipient' => $RFC822Head->{'recipient'},
};
sub new {
# Constructor of Sisimai::Data
# @param [Hash] argvs Data
# @return [Sisimai::Data] Structured email data
my $class = shift;
my $argvs = { @_ };
# Create email address object
my $as = Sisimai::Address->make($argvs->{'addresser'});
my $ar = Sisimai::Address->make({ 'address' => $argvs->{'recipient'} });
return undef unless ref $as eq 'Sisimai::Address';
return undef unless ref $ar eq 'Sisimai::Address';
my $thing = {
'addresser' => $as,
'recipient' => $ar,
'senderdomain' => $as->host,
'destination' => $ar->host,
'alias' => $argvs->{'alias'} || $ar->alias,
'token' => Sisimai::String->token($as, $ar, $argvs->{'timestamp'}),
};
# Create Sisimai::Time object
$thing->{'timestamp'} = gmtime Sisimai::Time->new($argvs->{'timestamp'});
$thing->{'timezoneoffset'} = $argvs->{'timezoneoffset'} // '+0000';
# Callback method
$thing->{'catch'} = $argvs->{'catch'} // undef;
my @v1 = (qw|
listid subject messageid smtpagent diagnosticcode diagnostictype deliverystatus
reason lhost rhost smtpcommand feedbacktype action softbounce replycode origin
|);
$thing->{ $_ } = $argvs->{ $_ } // '' for @v1;
$thing->{'replycode'} ||= Sisimai::SMTP::Reply->find($argvs->{'diagnosticcode'}) || '';
return bless($thing, __PACKAGE__);
}
sub make {
# Another constructor of Sisimai::Data
# @param [Hash] argvs Data and orders
# @option argvs [Sisimai::Message] Data Object
# @return [Array, Undef] List of Sisimai::Data or Undef if the
# argument is not Sisimai::Message object
my $class = shift;
my $argvs = { @_ };
return undef unless exists $argvs->{'data'};
return undef unless ref $argvs->{'data'} eq 'Sisimai::Message';
return undef unless $argvs->{'data'}->ds;
return undef unless $argvs->{'data'}->rfc822;
my $delivered1 = $argvs->{'delivered'} // 0;
my $messageobj = $argvs->{'data'};
my $rfc822data = $messageobj->rfc822;
my $fieldorder = { 'recipient' => [], 'addresser' => [] };
my $givenorder = $argvs->{'order'} ? $argvs->{'order'} : {};
my $objectlist = [];
# Decide the order of email headers: user specified or system default.
if( ref $givenorder eq 'HASH' && scalar keys %$givenorder ) {
# If the order of headers for searching is specified, use the order
# for detecting an email address.
for my $e ( keys %$fieldorder ) {
# The order should be "Array Reference".
next unless $givenorder->{ $e };
next unless ref $givenorder->{ $e } eq 'ARRAY';
next unless scalar @{ $givenorder->{ $e } };
push @{ $fieldorder->{ $e } }, @{ $givenorder->{ $e } };
}
}
for my $e ( keys %$fieldorder ) {
# If the order is empty, use default order.
next if scalar @{ $fieldorder->{ $e } };
# Load default order of each accessor.
$fieldorder->{ $e } = $AddrHeader->{ $e };
}
LOOP_DELIVERY_STATUS: for my $e ( @{ $messageobj->ds } ) {
# Create parameters for new() constructor.
my $p = {
'catch' => $messageobj->catch // undef,
'lhost' => $e->{'lhost'} // '',
'rhost' => $e->{'rhost'} // '',
'alias' => $e->{'alias'} // '',
'action' => $e->{'action'} // '',
'reason' => $e->{'reason'} // '',
'replycode' => $e->{'replycode'} // '',
'smtpagent' => $e->{'agent'} // '',
'recipient' => $e->{'recipient'} // '',
'softbounce' => $e->{'softbounce'} // '',
'smtpcommand' => $e->{'command'} // '',
'feedbacktype' => $e->{'feedbacktype'} // '',
'diagnosticcode' => $e->{'diagnosis'} // '',
'diagnostictype' => $e->{'spec'} // '',
'deliverystatus' => $e->{'status'} // '',
};
unless( $delivered1 ) {
# Skip if the value of "deliverystatus" begins with "2." such as 2.1.5
next if index($p->{'deliverystatus'}, '2.') == 0;
}
EMAIL_ADDRESS: {
# Detect email address from message/rfc822 part
for my $f ( @{ $fieldorder->{'addresser'} } ) {
# Check each header in message/rfc822 part
my $h = lc $f;
next unless exists $rfc822data->{ $h };
next unless $rfc822data->{ $h };
my $j = Sisimai::Address->find($rfc822data->{ $h }) || [];
next unless scalar @$j;
$p->{'addresser'} = $j->[0];
last;
}
unless( $p->{'addresser'} ) {
# Fallback: Get the sender address from the header of the bounced
# email if the address is not set at loop above.
my $j = Sisimai::Address->find($messageobj->{'header'}->{'to'}) || [];
$p->{'addresser'} = $j->[0] if scalar @$j;
}
}
next unless $p->{'addresser'};
next unless $p->{'recipient'};
TIMESTAMP: {
# Convert from a time stamp or a date string to a machine time.
my $datestring = undef;
my $zoneoffset = 0;
my @datevalues; push @datevalues, $e->{'date'} if $e->{'date'};
# Date information did not exist in message/delivery-status part,...
for my $f ( @{ $RFC822Head->{'date'} } ) {
# Get the value of Date header or other date related header.
next unless $rfc822data->{ $f };
push @datevalues, $rfc822data->{ $f };
}
# Set "date" getting from the value of "Date" in the bounce message
push @datevalues, $messageobj->{'header'}->{'date'} if scalar(@datevalues) < 2;
while( my $v = shift @datevalues ) {
# Parse each date value in the array
$datestring = Sisimai::DateTime->parse($v);
last if $datestring;
}
if( defined $datestring && $datestring =~ /\A(.+)[ ]+([-+]\d{4})\z/ ) {
# Get the value of timezone offset from $datestring
# Wed, 26 Feb 2014 06:05:48 -0500
$datestring = $1;
$zoneoffset = Sisimai::DateTime->tz2second($2);
$p->{'timezoneoffset'} = $2;
}
eval {
# Convert from the date string to an object then calculate time
# zone offset.
my $t = Sisimai::Time->strptime($datestring, '%a, %d %b %Y %T');
$p->{'timestamp'} = ($t->epoch - $zoneoffset) // undef;
};
}
next unless $p->{'timestamp'};
OTHER_TEXT_HEADERS: {
# Scan "Received:" header of the original message
my $recvheader = $argvs->{'data'}->{'header'}->{'received'} || [];
if( scalar @$recvheader ) {
# Get localhost and remote host name from Received header.
$e->{'lhost'} ||= shift @{ Sisimai::RFC5322->received($recvheader->[0]) };
$e->{'rhost'} ||= pop @{ Sisimai::RFC5322->received($recvheader->[-1]) };
}
for my $v ('rhost', 'lhost') {
$p->{ $v } =~ y/[]()//d; # Remove square brackets and curly brackets from the host variable
$p->{ $v } =~ s/\A.+=//; # Remove string before "="
chop $p->{ $v } if substr($p->{ $v }, -1, 1) eq "\r"; # Remove CR at the end of the value
# Check space character in each value and get the first element
$p->{ $v } = (split(' ', $p->{ $v }, 2))[0] if rindex($p->{ $v }, ' ') > -1;
chop $p->{ $v } if substr($p->{ $v }, -1, 1) eq '.'; # Remove "." at the end of the value
}
# Subject: header of the original message
$p->{'subject'} = $rfc822data->{'subject'} // '';
chop $p->{'subject'} if substr($p->{'subject'}, -1, 1) eq "\r";
if( $p->{'listid'} = $rfc822data->{'list-id'} // '' ) {
# Get the value of List-Id header: "List name <list-id@example.org>"
$p->{'listid'} = $1 if $p->{'listid'} =~ /\A.*([<].+[>]).*\z/;
$p->{'listid'} =~ y/<>//d;
chop $p->{'listid'} if substr($p->{'listid'}, -1, 1) eq "\r";
$p->{'listid'} = '' if rindex($p->{'listid'}, ' ') > -1;
}
if( $p->{'messageid'} = $rfc822data->{'message-id'} // '' ) {
# Leave only string inside of angle brackets(<>)
$p->{'messageid'} = $1 if $p->{'messageid'} =~ /\A([^ ]+)[ ].*/;
$p->{'messageid'} = $1 if $p->{'messageid'} =~ /[<]([^ ]+?)[>]/;
}
CHECK_DELIVERY_STATUS_VALUE: {
# Cleanup the value of "Diagnostic-Code:" header
chop $p->{'diagnosticcode'} if substr($p->{'diagnosticcode'}, -1, 1) eq "\r";
if( $p->{'diagnosticcode'} ) {
# Count the number of D.S.N. and SMTP Reply Code
my $vs = Sisimai::SMTP::Status->find($p->{'diagnosticcode'});
my $vr = Sisimai::SMTP::Reply->find($p->{'diagnosticcode'});
my $vm = 0;
if( $vs ) {
# How many times does the D.S.N. appeared
$vm += 1 while $p->{'diagnosticcode'} =~ /\b\Q$vs\E\b/g;
$p->{'deliverystatus'} = $vs if $vs =~ /\A[45][.][1-9][.][1-9]\z/;
}
if( $vr ) {
# How many times does the SMTP reply code appeared
$vm += 1 while $p->{'diagnosticcode'} =~ /\b$vr\b/g;
$p->{'replycode'} ||= $vr;
}
if( $vm > 2 ) {
# Build regular expression for removing string like '550-5.1.1'
# from the value of "diagnosticcode"
my $re = qr/[ ]$vr[- ](?:\Q$vs\E)?/;
# 550-5.7.1 [192.0.2.222] Our system has detected that this message is
# 550-5.7.1 likely unsolicited mail. To reduce the amount of spam sent to Gmail,
# 550-5.7.1 this message has been blocked. Please visit
# 550 5.7.1 https://support.google.com/mail/answer/188131 for more information.
$p->{'diagnosticcode'} =~ s/$re/ /g;
$p->{'diagnosticcode'} = Sisimai::String->sweep($p->{'diagnosticcode'});
}
}
$p->{'diagnostictype'} ||= 'X-UNIX' if $p->{'reason'} eq 'mailererror';
$p->{'diagnostictype'} ||= 'SMTP' unless $p->{'reason'} =~ /\A(?:feedback|vacation)\z/;
}
# Check the value of SMTP command
$p->{'smtpcommand'} = '' unless $p->{'smtpcommand'} =~ /\A(?:EHLO|HELO|MAIL|RCPT|DATA|QUIT)\z/;
$p->{'origin'} = $argvs->{'origin'}; # Set the path to the original email
# Check "Action:" field
next if length $p->{'action'};
if( $p->{'reason'} eq 'expired' ) {
# Action: delayed
$p->{'action'} = 'delayed';
} elsif( index($p->{'deliverystatus'}, '5') == 0 || index($p->{'deliverystatus'}, '4') == 0 ) {
# Action: failed
$p->{'action'} = 'failed';
}
}
next unless my $o = __PACKAGE__->new(%$p);
if( $o->reason eq '' || exists $RetryIndex->{ $o->reason } ) {
# Decide the reason of email bounce
my $r; $r = Sisimai::Rhost->get($o) if Sisimai::Rhost->match($o->rhost);
$r ||= Sisimai::Reason->get($o);
$r ||= 'undefined';
$o->reason($r);
}
if( $o->reason eq 'delivered' || $o->reason eq 'feedback' || $o->reason eq 'vacation' ) {
# The value of reason is "delivered", "vacation" or "feedback".
$o->softbounce(-1);
$o->replycode('') unless $o->reason eq 'delivered';
} else {
# Bounce message which reason is "feedback" or "vacation" does
# not have the value of "deliverystatus".
unless( length $o->softbounce ) {
# Set the value of softbounce
my $textasargv = $p->{'deliverystatus'}.' '.$p->{'diagnosticcode'};
substr($textasargv, 0, 1, '') if substr($textasargv, 0, 1) eq ' ';
my $softorhard = Sisimai::SMTP::Error->soft_or_hard($o->reason, $textasargv);
if( $softorhard ) {
# Returned value is "soft" or "hard"
$o->softbounce($softorhard eq 'soft' ? 1 : 0);
} else {
# Returned value is an empty string or undef
$o->softbounce(-1);
}
}
unless( $o->deliverystatus ) {
# Set pseudo status code
my $textasargv = $o->replycode.' '.$p->{'diagnosticcode'};
substr($textasargv, 0, 1, '') if substr($textasargv, 0, 1) eq ' ';
my $getchecked = Sisimai::SMTP::Error->is_permanent($textasargv);
my $tmpfailure = defined $getchecked ? ( $getchecked == 1 ? 0 : 1 ) : 0;
if( my $pseudocode = Sisimai::SMTP::Status->code($o->reason, $tmpfailure) ) {
# Set the value of "deliverystatus" and "softbounce".
$o->deliverystatus($pseudocode);
if( $o->softbounce == -1 ) {
# Set the value of "softbounce" again when the value is -1
if( my $softorhard = Sisimai::SMTP::Error->soft_or_hard($o->reason, $pseudocode) ) {
# Returned value is "soft" or "hard"
$o->softbounce($softorhard eq 'soft' ? 1 : 0);
} else {
# Returned value is an empty string or undef
$o->softbounce(-1);
}
}
}
}
if( $o->replycode ) {
# Check both of the first digit of "deliverystatus" and "replycode"
my $d1 = substr($o->deliverystatus, 0, 1);
my $r1 = substr($o->replycode, 0, 1);
$o->replycode('') unless $d1 eq $r1;
}
}
push @$objectlist, $o;
} # End of for(LOOP_DELIVERY_STATUS)
return $objectlist;
}
sub damn {
# Convert from object to hash reference
# @return [Hash] Data in Hash reference
my $self = shift;
my $data = undef;
eval {
my $v = {};
state $stringdata = [qw|
token lhost rhost listid alias reason subject messageid smtpagent
smtpcommand destination diagnosticcode senderdomain deliverystatus
timezoneoffset feedbacktype diagnostictype action replycode catch
softbounce origin
|];
for my $e ( @$stringdata ) {
# Copy string data
$v->{ $e } = $self->$e // '';
}
$v->{'addresser'} = $self->addresser->address;
$v->{'recipient'} = $self->recipient->address;
$v->{'timestamp'} = $self->timestamp->epoch;
$data = $v;
};
return $data;
}
sub dump {
# Data dumper
# @param [String] type Data format: json, yaml
# @return [String, Undef] Dumped data or Undef if the value of first
# argument is neither "json" nor "yaml"
my $self = shift;
my $type = shift || 'json';
return undef unless $type =~ /\A(?:json|yaml)\z/;
my $referclass = 'Sisimai::Data::'.uc($type);
my $modulepath = 'Sisimai/Data/'.uc($type).'.pm';
require $modulepath;
return $referclass->dump($self);
}
1;
__END__
=encoding utf-8
=head1 NAME
Sisimai::Data - Parsed data object
=head1 SYNOPSIS
use Sisimai::Data;
my $data = Sisimai::Data->make('data' => <Sisimai::Message> object);
for my $e ( @$data ) {
print $e->reason; # userunknown, mailboxfull, and so on.
print $e->recipient->address; # (Sisimai::Address) envelope recipient address
print $e->bonced->ymd # (Sisimai::Time) Date of bounce
}
=head1 DESCRIPTION
Sisimai::Data generate parsed data from Sisimai::Message object.
=head1 CLASS METHODS
=head2 C<B<make(I<Hash>)>>
C<make> generate parsed data and returns an array reference which are
including Sisimai::Data objects.
my $mail = Sisimai::Mail->new('/var/mail/root');
while( my $r = $mail->read ) {
my $mesg = Sisimai::Message->new('data' => $r);
my $data = Sisimai::Data->make('data' => $mesg);
for my $e ( @$data ) {
print $e->reason; # userunknown, mailboxfull, and so on.
print $e->recipient->address; # (Sisimai::Address) envelope recipient address
print $e->timestamp->ymd # (Sisimai::Time) Date of the email bounce
}
}
If you want to get bounce records which reason is "delivered", set "delivered"
option to make() method like the following:
my $data = Sisimai::Data->make('data' => $mesg, 'delivered' => 1);
Beginning from v4.19.0, `hook` argument is available to callback user defined
method like the following codes:
my $call = sub {
my $argv = shift;
my $fish = { 'x-mailer' => '' };
if( $argv->{'message'} =~ /^X-Mailer:\s*(.+)$/m ) {
$fish->{'x-mailer'} = $1;
}
return $fish;
};
my $mesg = Sisimai::Message->new('data' => $mailtxt, 'hook' => $call);
my $data = Sisimai::Data->make('data' => $mesg);
for my $e ( @$data ) {
print $e->catch->{'x-mailer'}; # Apple Mail (2.1283)
}
=head1 INSTANCE METHODS
=head2 C<B<damn()>>
C<damn> convert the object to a hash reference.
my $hash = $self->damn;
print $hash->{'recipient'}; # user@example.jp
print $hash->{'timestamp'}; # 1393940000
=head1 PROPERTIES
Sisimai::Data have the following properties:
=head2 C<action> (I<String>)
C<action> is the value of Action: field in a bounce email message such as
C<failed> or C<delayed>.
Action: failed
=head2 C<addresser> (I<Sisimai::Address)>
C<addressser> is L<Sisimai::Address> object generated from the sender address.
When Sisimai::Data object is dumped as JSON, this value converted to an email
address. Sisimai::Address object have the following accessors:
=over
=item - user() - the local part of the address
=item - host() - the domain part of the address
=item - address() - email address
=item - verp() - variable envelope return path
=item - alias() - alias of the address
=back
From: "Kijitora Cat" <kijitora@example.org>
=head2 C<alias> (I<String>)
C<alias> is an alias address of the recipient. When the Original-Recipient:
field or C<expanded from "address"> string did not exist in a bounce message,
this value is empty.
Original-Recipient: rfc822;kijitora@example.org
"|IFS=' ' && exec /usr/local/bin/procmail -f- || exit 75 #kijitora"
(expanded from: <kijitora@neko.example.edu>)
=head2 C<deliverystatus> (I<String>)
C<deliverystatus> is the value of Status: field in a bounce message. When the
message has no Status: field, Sisimai set pseudo value like 5.0.9XX to this
value. The range of values only C<4.x.x> or C<5.x.x>.
Status: 5.0.0 (permanent failure)
=head2 C<destination> (I<String>)
C<destination> is the domain part of the recipient address. This value is the
same as the return value from host() method of C<recipient> accessor.
=head2 C<diagnosticcode> (I<String>)
C<diagnosticcode> is an error message picked from Diagnostic-Code: field or
message body in a bounce message. This value and the value of C<diagnostictype>,
C<action>, C<deliverystatus>, C<replycode>, and C<smtpcommand> will be referred
by L<Sisimai::Reason> to decide the bounce reason.
Diagnostic-Code: SMTP; 554 5.4.6 Too many hops
=head2 C<diagnostictype> (C<String>)
C<diagnostictype> is a type like C<SMTP> or C<X-Unix> picked from Diagnostic-Code:
field in a bounce message. When there is no Diagnostic-Code: field in the bounce
message, this value will be empty.
Diagnostic-Code: X-Unix; 255
=head2 C<feedbacktype> (I<String>)
C<feedbacktype> is the value of Feedback-Type: field like C<abuse>, C<fraud>,
C<opt-out> in a bounce message. When the message is not ARF format or the value
of C<reason> is not C<feedback>, this value will be empty.
Content-Type: message/feedback-report
Feedback-Type: abuse
User-Agent: SMP-FBL
=head2 C<lhost> (I<String>)
C<lhost> is a local MTA name to be used as a gateway for sending email message
or the value of Reporting-MTA field in a bounce message. When there is no
Reporting-MTA field in the bounce message, Sisimai try to get the value from
Received header.
Reporting-MTA: dns; mx4.smtp.example.co.jp
=head2 C<listid> (I<String>)
C<listid> is the value of List-Id header of the original message. When there
is no List-Id field in the original message or the bounce message did not
include the original message, this value will be empty.
List-Id: Mailman mailing list management users
=head2 C<messageid> (I<String>)
C<messageid> is the value of Message-Id header of the original message. When
the original message did not include Message-Id: header or the bounce message
did not include the original message, this value will be empty.
Message-Id: <201310160515.r9G5FZh9018575@smtpgw.example.jp>
=head2 C<origin> (I<Path to the original email file>)
C<origin> is the path to the original email file of the parsed results. When
the original email data were input from STDIN, the value is C<<STDIN>>, were
input from a variable, the value is C<<MEMORY>>. This accessor method has been
implemented at v4.25.6.
=head2 C<recipient> (I<Sisimai::Address)>
C<recipient> is L<Sisimai::Address> object generated from the recipient address.
When Sisimai::Data object is dumped as JSON, this value converted to an email
address. Sisimai::Address object have the following accessors:
=over
=item - user() - the local part of the address
=item - host() - the domain part of the address
=item - address() - email address
=item - verp() - variable envelope return path
=item - alias() - alias of the address
=back
Final-Recipient: RFC822; shironeko@example.ne.jp
X-Failed-Recipients: kijitora@example.ed.jp
=head2 C<reason> (I<String>)
C<reason> is the value of bounce reason Sisimai detected. When this value is
C<undefined> or C<onhold>, it means that Sisimai could not decide the reason.
All the reasons Sisismai can detect are available at L<Sisimai::Reason> or web
site L<https://libsisimai.org/en/reason/>.
=head2 C<replycode> (I<Integer>)
C<replycode> is the value of SMTP reply code picked from the error message or
the value of Diagnostic-Code: field in a bounce message. The range of values is
only 4xx or 5xx.
----- The following addresses had permanent fatal errors -----
<userunknown@libsisimai.org>
(reason: 550 5.1.1 <userunknown@libsisimai.org>... User Unknown)
=head2 C<rhost> (I<String>)
C<rhost> is a remote MTA name which has rejected the message you sent or the
value of Remote-MTA: field in a bounce message. When there is no Remote-MTA
field in the bounce message, Sisimai try to get the value from Received header.
Remote-MTA: DNS; g5.example.net
=head2 C<senderdomain> (I<String>)
C<senderdomain> is the domain part of the sender address. This value is the same
as the return value from host() method of addresser accessor.
=head2 C<smtpagent> (I<String>)
C<smtpagent> is a module name to be used for detecting bounce reason. For
example, when the value is C<Sendmail>, Sisimai used L<Sisimai::Lhost::Sendmail>
to get the recipient address and other delivery status information from a
bounce message.
=head2 C<smtpcommand> (I<String>)
C<smtpcommand> is a SMTP command name picked from the error message or the value
of Diagnostic-Code: field in a bounce message. When there is no SMTP command in
the bounce message, this value will be empty. The list of values is C<HELO>,
C<EHLO>, C<MAIL>, C<RCPT>, and C<DATA>.
<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)
=head2 C<softbounce> (I<Integer>)
The value of C<softbounce> indicates whether the reason of the bounce is soft
bounce or hard bounce. This accessor has added in Sisimai 4.1.28. The range of
the values are the followings:
=over
=item 1 = Soft bounce
=item 0 = Hard bounce
=item -1 = Sisimai could not decide
=back
=head2 C<subject> (I<String>)
C<subject> is the value of Subject header of the original message. When the
original message which is included in a bounce email contains no Subject header
(removed by remote MTA), this value will be empty.
If the value of Subject header of the original message contain any multibyte
character (non-ASCII character), such as MIME encoded Japanese or German and so
on, the value of subject in parsed data is encoded with UTF-8 again.
=head2 C<token> (I<String>)
C<token> is an identifier of each email-bounce. The token string is created from
the sender email address (addresser) and the recipient email address (recipient)
and the machine time of the date in a bounce message as an MD5 hash value.
The token value is generated at C<token()> method of L<Sisimai::String> class.
If you want to get the same token string at command line, try to run the
following command:
% printf "\x02%s\x1e%s\x1e%d\x03" sender@example.jp recipient@example.org `date '+%s'` | md5
714d72dfd972242ad04f8053267e7365
=head2 C<timestamp> (I<Sisimai::Time>)
C<timestamp> is the date which email has bounced as a L<Sisima::Time> (Child
class of Time::Piece) object. When Sisimai::Data object is dumped as JSON, this
value will be converted to an UNIX machine time (32 bits integer).
Arrival-Date: Thu, 29 Apr 2009 23:45:33 +0900
=head2 C<timezomeoffset> (I<String>)
C<timezoneoffset> is a time zone offset of a bounce email which its email has
bounced. The format of this value is String like C<+0900>, C<-0200>.
If Sisimai has failed to get a value of time zone offset, this value will be
set as C<+0000>.
=head1 SEE ALSO
L<https://libsisimai.org/en/data/>
=head1 AUTHOR
azumakuniyuki
=head1 COPYRIGHT
Copyright (C) 2014-2020 azumakuniyuki, All rights reserved.
=head1 LICENSE
This software is distributed under The BSD 2-Clause License.
=cut