sisimai 5.0.3 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codecovio.yml +3 -1
  3. data/.github/workflows/rake-test.yml +7 -3
  4. data/ChangeLog.md +136 -0
  5. data/Makefile +4 -2
  6. data/README-JA.md +32 -22
  7. data/README.md +31 -21
  8. data/lib/sisimai/arf.rb +124 -213
  9. data/lib/sisimai/fact/json.rb +2 -2
  10. data/lib/sisimai/fact/yaml.rb +2 -2
  11. data/lib/sisimai/fact.rb +208 -173
  12. data/lib/sisimai/lda.rb +98 -0
  13. data/lib/sisimai/lhost/activehunter.rb +5 -4
  14. data/lib/sisimai/lhost/amazonses.rb +189 -305
  15. data/lib/sisimai/lhost/apachejames.rb +52 -55
  16. data/lib/sisimai/lhost/biglobe.rb +5 -6
  17. data/lib/sisimai/lhost/courier.rb +14 -12
  18. data/lib/sisimai/lhost/domino.rb +29 -29
  19. data/lib/sisimai/lhost/dragonfly.rb +113 -0
  20. data/lib/sisimai/lhost/einsundeins.rb +7 -8
  21. data/lib/sisimai/lhost/exchange2003.rb +10 -11
  22. data/lib/sisimai/lhost/exchange2007.rb +115 -104
  23. data/lib/sisimai/lhost/exim.rb +236 -246
  24. data/lib/sisimai/lhost/ezweb.rb +47 -55
  25. data/lib/sisimai/lhost/fml.rb +6 -7
  26. data/lib/sisimai/lhost/gmail.rb +36 -32
  27. data/lib/sisimai/lhost/gmx.rb +8 -20
  28. data/lib/sisimai/lhost/googlegroups.rb +13 -12
  29. data/lib/sisimai/lhost/googleworkspace.rb +94 -0
  30. data/lib/sisimai/lhost/imailserver.rb +11 -19
  31. data/lib/sisimai/lhost/interscanmss.rb +6 -5
  32. data/lib/sisimai/lhost/kddi.rb +7 -8
  33. data/lib/sisimai/lhost/mailfoundry.rb +6 -9
  34. data/lib/sisimai/lhost/mailmarshalsmtp.rb +6 -6
  35. data/lib/sisimai/lhost/messagingserver.rb +19 -17
  36. data/lib/sisimai/lhost/mfilter.rb +8 -7
  37. data/lib/sisimai/lhost/notes.rb +6 -8
  38. data/lib/sisimai/lhost/opensmtpd.rb +11 -9
  39. data/lib/sisimai/lhost/postfix.rb +29 -31
  40. data/lib/sisimai/lhost/qmail.rb +136 -112
  41. data/lib/sisimai/lhost/sendmail.rb +23 -22
  42. data/lib/sisimai/lhost/v5sendmail.rb +93 -64
  43. data/lib/sisimai/lhost/verizon.rb +6 -6
  44. data/lib/sisimai/lhost/x1.rb +4 -4
  45. data/lib/sisimai/lhost/x2.rb +4 -5
  46. data/lib/sisimai/lhost/x3.rb +5 -5
  47. data/lib/sisimai/lhost/x6.rb +4 -4
  48. data/lib/sisimai/lhost/zoho.rb +6 -6
  49. data/lib/sisimai/lhost.rb +21 -24
  50. data/lib/sisimai/mail/maildir.rb +1 -1
  51. data/lib/sisimai/mail/stdin.rb +1 -1
  52. data/lib/sisimai/message.rb +100 -153
  53. data/lib/sisimai/order.rb +22 -77
  54. data/lib/sisimai/reason/authfailure.rb +1 -4
  55. data/lib/sisimai/reason/badreputation.rb +3 -3
  56. data/lib/sisimai/reason/blocked.rb +7 -10
  57. data/lib/sisimai/reason/contenterror.rb +7 -1
  58. data/lib/sisimai/reason/exceedlimit.rb +1 -4
  59. data/lib/sisimai/reason/failedstarttls.rb +42 -0
  60. data/lib/sisimai/reason/filtered.rb +5 -4
  61. data/lib/sisimai/reason/hasmoved.rb +1 -2
  62. data/lib/sisimai/reason/hostunknown.rb +3 -3
  63. data/lib/sisimai/reason/mailboxfull.rb +2 -4
  64. data/lib/sisimai/reason/mailererror.rb +1 -2
  65. data/lib/sisimai/reason/mesgtoobig.rb +2 -4
  66. data/lib/sisimai/reason/norelaying.rb +3 -3
  67. data/lib/sisimai/reason/notaccept.rb +2 -3
  68. data/lib/sisimai/reason/notcompliantrfc.rb +10 -4
  69. data/lib/sisimai/reason/rejected.rb +2 -1
  70. data/lib/sisimai/reason/requireptr.rb +2 -2
  71. data/lib/sisimai/reason/securityerror.rb +1 -3
  72. data/lib/sisimai/reason/spamdetected.rb +6 -8
  73. data/lib/sisimai/reason/speeding.rb +1 -2
  74. data/lib/sisimai/reason/suppressed.rb +36 -0
  75. data/lib/sisimai/reason/suspend.rb +1 -3
  76. data/lib/sisimai/reason/systemerror.rb +5 -0
  77. data/lib/sisimai/reason/toomanyconn.rb +1 -2
  78. data/lib/sisimai/reason/userunknown.rb +1 -1
  79. data/lib/sisimai/reason/virusdetected.rb +5 -6
  80. data/lib/sisimai/reason.rb +82 -78
  81. data/lib/sisimai/rfc1123.rb +152 -0
  82. data/lib/sisimai/rfc1894.rb +102 -62
  83. data/lib/sisimai/rfc2045.rb +2 -1
  84. data/lib/sisimai/rfc3464/thirdparty.rb +102 -0
  85. data/lib/sisimai/rfc3464.rb +224 -345
  86. data/lib/sisimai/rfc3834.rb +3 -3
  87. data/lib/sisimai/rfc5322.rb +7 -17
  88. data/lib/sisimai/rfc791.rb +69 -0
  89. data/lib/sisimai/rhost/aol.rb +36 -0
  90. data/lib/sisimai/rhost/apple.rb +95 -0
  91. data/lib/sisimai/rhost/cox.rb +84 -34
  92. data/lib/sisimai/rhost/facebook.rb +100 -0
  93. data/lib/sisimai/rhost/franceptt.rb +87 -83
  94. data/lib/sisimai/rhost/godaddy.rb +208 -45
  95. data/lib/sisimai/rhost/google.rb +22 -22
  96. data/lib/sisimai/rhost/gsuite.rb +42 -0
  97. data/lib/sisimai/rhost/iua.rb +5 -5
  98. data/lib/sisimai/rhost/kddi.rb +9 -7
  99. data/lib/sisimai/rhost/messagelabs.rb +37 -0
  100. data/lib/sisimai/rhost/microsoft.rb +60 -54
  101. data/lib/sisimai/rhost/mimecast.rb +44 -31
  102. data/lib/sisimai/rhost/nttdocomo.rb +5 -4
  103. data/lib/sisimai/rhost/outlook.rb +36 -0
  104. data/lib/sisimai/rhost/spectrum.rb +102 -41
  105. data/lib/sisimai/rhost/tencent.rb +48 -26
  106. data/lib/sisimai/rhost/yahooinc.rb +111 -0
  107. data/lib/sisimai/rhost.rb +65 -42
  108. data/lib/sisimai/smtp/command.rb +31 -21
  109. data/lib/sisimai/smtp/failure.rb +103 -0
  110. data/lib/sisimai/smtp/reply.rb +29 -24
  111. data/lib/sisimai/smtp/status.rb +36 -19
  112. data/lib/sisimai/smtp/transcript.rb +18 -18
  113. data/lib/sisimai/string.rb +0 -46
  114. data/lib/sisimai/version.rb +1 -1
  115. data/lib/sisimai.rb +0 -6
  116. data/set-of-emails/maildir/bsd/lhost-dragonfly-01.eml +36 -0
  117. data/set-of-emails/maildir/bsd/lhost-dragonfly-02.eml +32 -0
  118. data/set-of-emails/maildir/bsd/lhost-dragonfly-03.eml +32 -0
  119. data/set-of-emails/maildir/bsd/lhost-dragonfly-04.eml +31 -0
  120. data/set-of-emails/maildir/bsd/lhost-dragonfly-05.eml +32 -0
  121. data/set-of-emails/maildir/bsd/lhost-dragonfly-06.eml +32 -0
  122. data/set-of-emails/maildir/bsd/lhost-dragonfly-07.eml +32 -0
  123. data/set-of-emails/maildir/bsd/lhost-dragonfly-08.eml +32 -0
  124. data/set-of-emails/maildir/bsd/lhost-dragonfly-09.eml +32 -0
  125. data/set-of-emails/maildir/bsd/lhost-dragonfly-10.eml +32 -0
  126. data/set-of-emails/maildir/bsd/lhost-dragonfly-11.eml +32 -0
  127. data/set-of-emails/maildir/bsd/lhost-dragonfly-12.eml +32 -0
  128. data/set-of-emails/maildir/bsd/lhost-dragonfly-13.eml +32 -0
  129. data/set-of-emails/maildir/bsd/lhost-dragonfly-14.eml +32 -0
  130. data/set-of-emails/maildir/bsd/lhost-dragonfly-15.eml +32 -0
  131. data/set-of-emails/maildir/bsd/lhost-dragonfly-16.eml +32 -0
  132. data/set-of-emails/maildir/bsd/lhost-dragonfly-17.eml +32 -0
  133. data/set-of-emails/maildir/bsd/lhost-dragonfly-18.eml +32 -0
  134. data/set-of-emails/maildir/bsd/lhost-dragonfly-19.eml +32 -0
  135. data/set-of-emails/maildir/bsd/lhost-dragonfly-20.eml +32 -0
  136. data/set-of-emails/maildir/bsd/lhost-dragonfly-21.eml +33 -0
  137. data/set-of-emails/maildir/bsd/lhost-dragonfly-22.eml +32 -0
  138. data/set-of-emails/maildir/bsd/lhost-dragonfly-23.eml +32 -0
  139. data/set-of-emails/maildir/bsd/lhost-dragonfly-24.eml +32 -0
  140. data/set-of-emails/maildir/bsd/lhost-dragonfly-25.eml +33 -0
  141. data/set-of-emails/maildir/bsd/lhost-dragonfly-26.eml +33 -0
  142. data/set-of-emails/maildir/bsd/lhost-dragonfly-27.eml +47 -0
  143. data/set-of-emails/maildir/bsd/lhost-dragonfly-28.eml +47 -0
  144. data/set-of-emails/maildir/bsd/lhost-dragonfly-29.eml +32 -0
  145. data/set-of-emails/maildir/bsd/lhost-dragonfly-30.eml +32 -0
  146. data/set-of-emails/maildir/bsd/lhost-opensmtpd-10.eml +58 -0
  147. data/set-of-emails/maildir/bsd/lhost-opensmtpd-11.eml +58 -0
  148. data/set-of-emails/maildir/bsd/lhost-opensmtpd-12.eml +62 -0
  149. data/set-of-emails/maildir/bsd/lhost-opensmtpd-13.eml +62 -0
  150. data/set-of-emails/maildir/bsd/lhost-opensmtpd-14.eml +58 -0
  151. data/set-of-emails/maildir/bsd/lhost-opensmtpd-15.eml +63 -0
  152. data/set-of-emails/maildir/bsd/lhost-opensmtpd-16.eml +62 -0
  153. data/set-of-emails/maildir/bsd/lhost-opensmtpd-17.eml +63 -0
  154. data/set-of-emails/maildir/bsd/lhost-postfix-30.eml +81 -81
  155. data/set-of-emails/maildir/bsd/lhost-qmail-11.eml +29 -0
  156. data/set-of-emails/maildir/bsd/lhost-qmail-12.eml +29 -0
  157. data/set-of-emails/maildir/bsd/lhost-qmail-13.eml +29 -0
  158. data/set-of-emails/maildir/bsd/lhost-qmail-14.eml +34 -0
  159. data/set-of-emails/maildir/bsd/lhost-qmail-15.eml +30 -0
  160. data/set-of-emails/maildir/bsd/lhost-qmail-16.eml +31 -0
  161. data/set-of-emails/maildir/bsd/lhost-qmail-17.eml +38 -0
  162. data/set-of-emails/maildir/bsd/lhost-qmail-18.eml +31 -0
  163. data/set-of-emails/maildir/bsd/lhost-qmail-19.eml +31 -0
  164. data/set-of-emails/maildir/bsd/lhost-qmail-20.eml +46 -0
  165. data/set-of-emails/maildir/bsd/lhost-qmail-21.eml +41 -0
  166. data/set-of-emails/maildir/bsd/lhost-qmail-22.eml +42 -0
  167. data/set-of-emails/maildir/bsd/lhost-qmail-23.eml +43 -0
  168. data/set-of-emails/maildir/bsd/lhost-qmail-24.eml +43 -0
  169. data/set-of-emails/maildir/bsd/lhost-qmail-25.eml +54 -0
  170. data/set-of-emails/maildir/bsd/{lhost-aol-03.eml → rhost-aol-03.eml} +1264 -1264
  171. data/set-of-emails/maildir/bsd/{lhost-aol-04.eml → rhost-aol-04.eml} +1260 -1260
  172. data/set-of-emails/maildir/bsd/{lhost-aol-05.eml → rhost-aol-05.eml} +105 -105
  173. data/set-of-emails/maildir/bsd/{lhost-aol-06.eml → rhost-aol-06.eml} +105 -105
  174. data/set-of-emails/maildir/bsd/rhost-apple-01.eml +79 -0
  175. data/set-of-emails/maildir/bsd/rhost-apple-02.eml +81 -0
  176. data/set-of-emails/maildir/bsd/rhost-apple-03.eml +75 -0
  177. data/set-of-emails/maildir/bsd/rhost-apple-04.eml +74 -0
  178. data/set-of-emails/maildir/bsd/rhost-gsuite-01.eml +189 -0
  179. data/set-of-emails/maildir/bsd/rhost-gsuite-02.eml +180 -0
  180. data/set-of-emails/maildir/bsd/rhost-gsuite-03.eml +251 -0
  181. data/set-of-emails/maildir/bsd/rhost-gsuite-04.eml +211 -0
  182. data/set-of-emails/maildir/bsd/rhost-gsuite-05.eml +226 -0
  183. data/set-of-emails/maildir/bsd/rhost-gsuite-06.eml +257 -0
  184. data/set-of-emails/maildir/bsd/rhost-gsuite-07.eml +289 -0
  185. data/set-of-emails/maildir/bsd/rhost-gsuite-08.eml +231 -0
  186. data/set-of-emails/maildir/bsd/rhost-gsuite-09.eml +231 -0
  187. data/set-of-emails/maildir/bsd/rhost-gsuite-10.eml +254 -0
  188. data/set-of-emails/maildir/bsd/rhost-gsuite-11.eml +228 -0
  189. data/set-of-emails/maildir/bsd/rhost-gsuite-12.eml +271 -0
  190. data/set-of-emails/maildir/bsd/rhost-gsuite-13.eml +261 -0
  191. data/set-of-emails/maildir/bsd/rhost-gsuite-14.eml +273 -0
  192. data/set-of-emails/maildir/bsd/rhost-gsuite-15.eml +229 -0
  193. data/set-of-emails/maildir/bsd/{lhost-messagelabs-01.eml → rhost-messagelabs-01.eml} +93 -93
  194. data/set-of-emails/maildir/bsd/rhost-outlook-01.eml +72 -0
  195. data/set-of-emails/maildir/bsd/rhost-outlook-02.eml +72 -0
  196. data/set-of-emails/maildir/bsd/rhost-outlook-03.eml +72 -0
  197. data/set-of-emails/maildir/bsd/rhost-outlook-04.eml +79 -0
  198. data/set-of-emails/maildir/bsd/rhost-outlook-06.eml +75 -0
  199. data/set-of-emails/maildir/bsd/rhost-outlook-07.eml +70 -0
  200. data/set-of-emails/maildir/bsd/rhost-outlook-08.eml +70 -0
  201. data/set-of-emails/maildir/bsd/rhost-outlook-09.eml +56 -0
  202. data/set-of-emails/maildir/bsd/rhost-yahooinc-01.eml +80 -0
  203. data/set-of-emails/maildir/bsd/rhost-yahooinc-02.eml +83 -0
  204. data/set-of-emails/maildir/bsd/rhost-yahooinc-03.eml +125 -0
  205. data/set-of-emails/maildir/tmp/arf-22.eml +49 -0
  206. data/set-of-emails/maildir/tmp/arf-23.eml +49 -0
  207. data/set-of-emails/maildir/tmp/arf-24.eml +50 -0
  208. data/set-of-emails/maildir/tmp/lhost-exim-07.eml +28 -0
  209. metadata +136 -56
  210. data/lib/sisimai/lhost/amavis.rb +0 -163
  211. data/lib/sisimai/lhost/amazonworkmail.rb +0 -127
  212. data/lib/sisimai/lhost/aol.rb +0 -125
  213. data/lib/sisimai/lhost/barracuda.rb +0 -92
  214. data/lib/sisimai/lhost/bigfoot.rb +0 -125
  215. data/lib/sisimai/lhost/facebook.rb +0 -188
  216. data/lib/sisimai/lhost/gsuite.rb +0 -194
  217. data/lib/sisimai/lhost/mailru.rb +0 -214
  218. data/lib/sisimai/lhost/mcafee.rb +0 -109
  219. data/lib/sisimai/lhost/messagelabs.rb +0 -119
  220. data/lib/sisimai/lhost/mxlogic.rb +0 -198
  221. data/lib/sisimai/lhost/office365.rb +0 -252
  222. data/lib/sisimai/lhost/outlook.rb +0 -129
  223. data/lib/sisimai/lhost/powermta.rb +0 -118
  224. data/lib/sisimai/lhost/receivingses.rb +0 -126
  225. data/lib/sisimai/lhost/sendgrid.rb +0 -150
  226. data/lib/sisimai/lhost/surfcontrol.rb +0 -105
  227. data/lib/sisimai/lhost/x4.rb +0 -269
  228. data/lib/sisimai/lhost/x5.rb +0 -112
  229. data/lib/sisimai/lhost/yahoo.rb +0 -102
  230. data/lib/sisimai/lhost/yandex.rb +0 -118
  231. data/lib/sisimai/mda.rb +0 -121
  232. data/lib/sisimai/smtp/error.rb +0 -119
  233. /data/set-of-emails/maildir/bsd/{lhost-googlegroups-15.eml → lhost-googleworkspace-01.eml} +0 -0
  234. /data/set-of-emails/maildir/bsd/{lhost-x4-08.eml → lhost-x2-06.eml} +0 -0
  235. /data/set-of-emails/maildir/bsd/{lhost-gsuite-01.eml → rfc3464-51.eml} +0 -0
  236. /data/set-of-emails/maildir/bsd/{lhost-gsuite-03.eml → rfc3464-52.eml} +0 -0
  237. /data/set-of-emails/maildir/bsd/{lhost-gsuite-04.eml → rfc3464-53.eml} +0 -0
  238. /data/set-of-emails/maildir/bsd/{lhost-gsuite-05.eml → rfc3464-54.eml} +0 -0
  239. /data/set-of-emails/maildir/bsd/{lhost-gsuite-06.eml → rfc3464-55.eml} +0 -0
  240. /data/set-of-emails/maildir/bsd/{lhost-gsuite-07.eml → rfc3464-56.eml} +0 -0
  241. /data/set-of-emails/maildir/bsd/{lhost-gsuite-08.eml → rfc3464-57.eml} +0 -0
  242. /data/set-of-emails/maildir/bsd/{lhost-gsuite-09.eml → rfc3464-58.eml} +0 -0
  243. /data/set-of-emails/maildir/bsd/{lhost-gsuite-10.eml → rfc3464-59.eml} +0 -0
  244. /data/set-of-emails/maildir/bsd/{lhost-gsuite-11.eml → rfc3464-60.eml} +0 -0
  245. /data/set-of-emails/maildir/bsd/{lhost-gsuite-12.eml → rfc3464-61.eml} +0 -0
  246. /data/set-of-emails/maildir/bsd/{lhost-gsuite-13.eml → rfc3464-62.eml} +0 -0
  247. /data/set-of-emails/maildir/bsd/{lhost-gsuite-14.eml → rfc3464-63.eml} +0 -0
  248. /data/set-of-emails/maildir/bsd/{lhost-gsuite-15.eml → rfc3464-64.eml} +0 -0
  249. /data/set-of-emails/maildir/bsd/{lhost-gsuite-02.eml → rfc3464-65.eml} +0 -0
  250. /data/set-of-emails/maildir/bsd/{lhost-aol-01.eml → rhost-aol-01.eml} +0 -0
  251. /data/set-of-emails/maildir/bsd/{lhost-aol-02.eml → rhost-aol-02.eml} +0 -0
  252. /data/set-of-emails/maildir/bsd/{lhost-facebook-03.eml → rhost-facebook-03.eml} +0 -0
  253. /data/set-of-emails/maildir/bsd/{lhost-facebook-04.eml → rhost-facebook-04.eml} +0 -0
  254. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-02.eml → rhost-messagelabs-02.eml} +0 -0
  255. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-03.eml → rhost-messagelabs-03.eml} +0 -0
  256. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-37.eml +0 -0
  257. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-38.eml +0 -0
  258. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-39.eml +0 -0
@@ -0,0 +1,98 @@
1
+ module Sisimai
2
+ # Sisimai::LDA - Error message decoder for LDA
3
+ module LDA
4
+ class << self
5
+ LocalAgent = {
6
+ # Each error message should be a lower-cased string
7
+ # dovecot/src/deliver/deliver.c
8
+ # 11: #define DEFAULT_MAIL_REJECTION_HUMAN_REASON \
9
+ # 12: "Your message to <%t> was automatically rejected:%n%r"
10
+ "dovecot" => ["Your message to <", "> was automatically rejected:"],
11
+ "mail.local" => ["mail.local: "],
12
+ "procmail" => ["procmail: ", "/procmail "],
13
+ "maildrop" => ["maildrop: "],
14
+ "vpopmail" => ["vdelivermail: "],
15
+ "vmailmgr" => ["vdeliver: "],
16
+ }.freeze
17
+
18
+ MessagesOf = {
19
+ # Each error message should be a lower-cased string
20
+ "dovecot" => {
21
+ # dovecot/src/deliver/mail-send.c:94
22
+ "mailboxfull" => [
23
+ "not enough disk space",
24
+ "quota exceeded", # Dovecot 1.2 dovecot/src/plugins/quota/quota.c
25
+ "quota exceeded (mailbox for user is full)", # dovecot/src/plugins/quota/quota.c
26
+ ],
27
+ "userunknown" => ["mailbox doesn't exist: "],
28
+ },
29
+ "mail.local" => {
30
+ "mailboxfull" => [
31
+ "disc quota exceeded",
32
+ "mailbox full or quota exceeded",
33
+ ],
34
+ "systemerror" => ["temporary file write error"],
35
+ "userunknown" => [
36
+ ": invalid mailbox path",
37
+ ": unknown user:",
38
+ ": user missing home directory",
39
+ ": user unknown",
40
+ ],
41
+ },
42
+ "procmail" => {
43
+ "mailboxfull" => ["quota exceeded while writing", "user over quota"],
44
+ "systemerror" => ["service unavailable"],
45
+ "systemfull" => ["no space left to finish writing"],
46
+ },
47
+ "maildrop" => {
48
+ "userunknown" => ["cannot find system user", "invalid user specified."],
49
+ "mailboxfull" => ["maildir over quota."],
50
+ },
51
+ "vpopmail" => {
52
+ "filtered" => ["user does not exist, but will deliver to "],
53
+ "mailboxfull" => ["domain is over quota", "user is over quota"],
54
+ "suspend" => ["account is locked email bounced"],
55
+ "userunknown" => ["sorry, no mailbox here by that name."],
56
+ },
57
+ "vmailmgr" => {
58
+ "mailboxfull" => ["delivery failed due to system quota violation"],
59
+ "userunknown" => [
60
+ "invalid or unknown base user or domain",
61
+ "invalid or unknown virtual user",
62
+ "user name does not refer to a virtual user",
63
+ ],
64
+ },
65
+ }.freeze
66
+
67
+ # @abstract Decodes the message body and return the LDA name, the reason, and the error message
68
+ # @param [Sisimai::Fact] argvs Decoded email object
69
+ # @return [String] Bounce reason
70
+ def find(argvs)
71
+ return nil if argvs.nil?
72
+ return "" if argvs["diagnosticcode"].empty?
73
+ return "" if argvs["command"] != "" && argvs["command"] != "DATA"
74
+
75
+ deliversby = "" # [String] Local Delivery Agent name
76
+ reasontext = "" # [String] Error reason
77
+ issuedcode = argvs["diagnosticcode"].downcase
78
+
79
+ LocalAgent.each_key do |e|
80
+ # Find a lcoal delivery agent name from the entire message body
81
+ next unless LocalAgent[e].any? { |a| issuedcode.include?(a) }
82
+ deliversby = e; break
83
+ end
84
+ return "" if deliversby.empty?
85
+
86
+ MessagesOf[deliversby].each_key do |e|
87
+ # The key is a bounce reason name
88
+ next unless MessagesOf[deliversby][e].any? { |a| issuedcode.include?(a) }
89
+ reasontext = e; break
90
+ end
91
+
92
+ reasontext = "mailererror" if reasontext.empty?
93
+ return reasontext
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -1,5 +1,6 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Activehunter parses a bounce email which created by TransWARE Active!hunter.
2
+ # Sisimai::Lhost::Activehunter decodes a bounce email which created by QUALITIA Active!hunter
3
+ # https://www.qualitia.com/jp/product/ah/function.html.
3
4
  # Methods in the module are called from only Sisimai::Message.
4
5
  module Activehunter
5
6
  class << self
@@ -9,11 +10,11 @@ module Sisimai::Lhost
9
10
  Boundaries = ['Content-Type: message/rfc822'].freeze
10
11
  StartingOf = { message: [' ----- The following addresses had permanent fatal errors -----'] }.freeze
11
12
 
12
- # Parse bounce messages from TransWARE Active!hunter
13
+ # @abstract decodes the bounce message from QUALITIA Active!hunter
13
14
  # @param [Hash] mhead Message headers of a bounce email
14
15
  # @param [String] mbody Message body of a bounce email
15
16
  # @return [Hash] Bounce data list and message/rfc822 part
16
- # @return [Nil] it failed to parse or the arguments are missing
17
+ # @return [Nil] it failed to decode or the arguments are missing
17
18
  def inquire(mhead, mbody)
18
19
  # :from => %r/\A"MAILER-DAEMON"/,
19
20
  # :subject => %r/FAILURE NOTICE :/,
@@ -47,7 +48,7 @@ module Sisimai::Lhost
47
48
 
48
49
  if e.start_with?('>>> ') && e.index('@') > 1
49
50
  # >>> kijitora@example.org <kijitora@example.org>
50
- if v['recipient']
51
+ if v["recipient"] != ""
51
52
  # There are multiple recipient addresses in the message body.
52
53
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
53
54
  v = dscontents[-1]
@@ -1,335 +1,219 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::AmazonSES parses a bounce email which created by Amazon Simple Email Service.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::AmazonSES decodes a bounce email which created by Amazon Simple Email Service
3
+ # https://aws.amazon.com/ses/. Methods in the module are called from only Sisimai::Message.
4
4
  module AmazonSES
5
+ # ---------------------------------------------------------------------------------------------
6
+ # "notificationType": "Bounce"
7
+ # https://docs.aws.amazon.com/ses/latest/dg/notification-contents.html#bounce-object
8
+ #
9
+ # Bounce types
10
+ # The bounce object contains a bounce type of Undetermined, Permanent, or Transient. The
11
+ # Permanent and Transient bounce types can also contain one of several bounce subtypes.
12
+ #
13
+ # When you receive a bounce notification with a bounce type of Transient, you might be
14
+ # able to send email to that recipient in the future if the issue that caused the message
15
+ # to bounce is resolved.
16
+ #
17
+ # When you receive a bounce notification with a bounce type of Permanent, it's unlikely
18
+ # that you'll be able to send email to that recipient in the future. For this reason, you
19
+ # should immediately remove the recipient whose address produced the bounce from your
20
+ # mailing lists.
21
+ #
22
+ # "bounceType"/"bounceSubType" "Desription"
23
+ # Undetermined/Undetermined -- The bounce message didn't contain enough information for
24
+ # Amazon SES to determine the reason for the bounce.
25
+ #
26
+ # Permanent/General ---------- When you receive this type of bounce notification, you should
27
+ # immediately remove the recipient's email address from your
28
+ # mailing list.
29
+ # Permanent/NoEmail ---------- It was not possible to retrieve the recipient email address
30
+ # from the bounce message.
31
+ # Permanent/Suppressed ------- The recipient's email address is on the Amazon SES suppression
32
+ # list because it has a recent history of producing hard bounces.
33
+ # Permanent/OnAccountSuppressionList
34
+ # Amazon SES has suppressed sending to this address because it
35
+ # is on the account-level suppression list.
36
+ #
37
+ # Transient/General ---------- You might be able to send a message to the same recipient
38
+ # in the future if the issue that caused the message to bounce
39
+ # is resolved.
40
+ # Transient/MailboxFull ------ the recipient's inbox was full.
41
+ # Transient/MessageTooLarge -- message you sent was too large
42
+ # Transient/ContentRejected -- message you sent contains content that the provider doesn't allow
43
+ # Transient/AttachmentRejected the message contained an unacceptable attachment
5
44
  class << self
6
45
  require 'sisimai/lhost'
7
46
 
8
- # https://aws.amazon.com/ses/
9
- Indicators = Sisimai::Lhost.INDICATORS
10
- Boundaries = ['Content-Type: message/rfc822'].freeze
11
- StartingOf = {
12
- message: ['The following message to <', 'An error occurred while trying to deliver the mail'],
47
+ ReasonPair = {
48
+ "Supressed" => "suppressed",
49
+ "OnAccountSuppressionList" => "suppressed",
50
+ "General" => "onhold",
51
+ "MailboxFull" => "mailboxfull",
52
+ "MessageTooLarge" => "mesgtoobig",
53
+ "ContentRejected" => "contenterror",
54
+ "AttachmentRejected" => "securityerror",
13
55
  }.freeze
14
- MessagesOf = { 'expired' => ['Delivery expired'] }.freeze
15
56
 
16
- # Parse bounce messages from Amazon SES
57
+ # @abstract Decodes the bounce message from Amazon SES
17
58
  # @param [Hash] mhead Message headers of a bounce email
18
59
  # @param [String] mbody Message body of a bounce email
19
60
  # @return [Hash] Bounce data list and message/rfc822 part
20
- # @return [Nil] it failed to parse or the arguments are missing
61
+ # @return [Nil] it failed to decode or the arguments are missing
21
62
  def inquire(mhead, mbody)
22
- dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
23
- recipients = 0 # (Integer) The number of 'Final-Recipient' header
24
-
25
- if mbody.start_with?('{')
26
- # The message body is JSON string
27
- return nil unless mhead['x-amz-sns-message-id']
28
- return nil if mhead['x-amz-sns-message-id'].empty?
29
-
30
- # https://docs.aws.amazon.com/en_us/ses/latest/DeveloperGuide/notification-contents.html
31
- bouncetype = {
32
- 'Permanent' => { 'General' => '', 'NoEmail' => '', 'Suppressed' => '' },
33
- 'Transient' => {
34
- 'General' => '',
35
- 'MailboxFull' => 'mailboxfull',
36
- 'MessageTooLarge' => 'mesgtoobig',
37
- 'ContentRejected' => '',
38
- 'AttachmentRejected' => '',
39
- },
40
- }.freeze
41
- jsonstring = ''
42
- sespayload = nil
43
- foldedline = false
44
- bodyslices = mbody.split("\n")
45
-
46
- while e = bodyslices.shift do
47
- # Find JSON string from the message body
48
- next if e.empty?
49
- break if e == '--'
50
-
51
- # The line starts with " ", continued from !\n.
52
- e.lstrip! if foldedline
53
- foldedline = false
54
-
55
- if e.end_with?('!')
56
- # ... long long line ...![\n]
57
- e.chomp!('!')
58
- foldedline = true
59
- end
60
- jsonstring << e
61
- end
62
-
63
- begin
64
- if RUBY_PLATFORM.start_with?('java')
65
- # java-based ruby environment like JRuby.
66
- require 'jrjackson'
67
- jsonobject = JrJackson::Json.load(jsonstring)
68
-
69
- # 'Message' => '{"notificationType":"Bounce",...
70
- sespayload = JrJackson::Json.load(jsonobject['Message']) if jsonobject['Message']
71
- else
72
- # Matz' Ruby Implementation
73
- require 'oj'
74
- jsonobject = Oj.load(jsonstring)
75
-
76
- # 'Message' => '{"notificationType":"Bounce",...
77
- sespayload = Oj.load(jsonobject['Message']) if jsonobject['Message']
78
- end
79
- sespayload ||= jsonobject
80
-
81
- rescue StandardError => ce
82
- # Something wrong in decoding JSON
83
- warn ' ***warning: Failed to decode JSON: ' << ce.to_s
84
- return nil
63
+ return nil if mbody.include?("{") == false
64
+ return nil if mhead.has_key?("x-amz-sns-message-id") == false
65
+ return nil if mhead["x-amz-sns-message-id"].empty?
66
+
67
+ proceedsto = false
68
+ sespayload = mbody
69
+ while true
70
+ # Remote the following string begins with "--"
71
+ # --
72
+ # If you wish to stop receiving notifications from this topic, please click or visit the link below to unsubscribe:
73
+ # https://sns.us-west-2.amazonaws.com/unsubscribe.html?SubscriptionArn=arn:aws:sns:us-west-2:1...
74
+ p1 = mbody.index("\n\n--\n")
75
+ sespayload = mbody[0, p1] if p1
76
+ sespayload = sespayload.gsub("!\n ", "")
77
+ p2 = sespayload.index('"Message"')
78
+
79
+ if p2
80
+ # The JSON included in the email is a format like the following:
81
+ # {
82
+ # "Type" : "Notification",
83
+ # "MessageId" : "02f86d9b-eecf-573d-b47d-3d1850750c30",
84
+ # "TopicArn" : "arn:aws:sns:us-west-2:123456789012:SES-EJ-B",
85
+ # "Message" : "{\"notificationType\"...
86
+ sespayload = sespayload.gsub("\\", "")
87
+ p3 = sespayload.index("{", p2 + 9)
88
+ p4 = sespayload.index("\n", p2 + 9)
89
+ sespayload = sespayload[p3, p4 - p3]
90
+ sespayload = sespayload.chop if sespayload[-1, 1] == ","
91
+ sespayload = sespayload.chop if sespayload[-1, 1] == '"'
85
92
  end
86
93
 
87
- rfc822head = {} # (Hash) Check flags for headers in RFC822 part
88
- labeltable = {
89
- 'Bounce' => 'bouncedRecipients',
90
- 'Complaint' => 'complainedRecipients',
91
- }
92
- p = sespayload
93
- v = nil
94
-
95
- if %w[Bounce Complaint].index(p['notificationType'])
96
- # { "notificationType":"Bounce", "bounce": { "bounceType":"Permanent",...
97
- o = p[p['notificationType'].downcase].dup
98
- r = o[labeltable[p['notificationType']]] || []
99
-
100
- while e = r.shift do
101
- # 'bouncedRecipients' => [ { 'emailAddress' => 'bounce@si...' }, ... ]
102
- # 'complainedRecipients' => [ { 'emailAddress' => 'complaint@si...' }, ... ]
103
- next unless Sisimai::Address.is_emailaddress(e['emailAddress'])
104
-
105
- v = dscontents[-1]
106
- if v['recipient']
107
- # There are multiple recipient addresses in the message body.
108
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
109
- v = dscontents[-1]
110
- end
111
- recipients += 1
112
- v['recipient'] = e['emailAddress']
113
-
114
- if p['notificationType'] == 'Bounce'
115
- # 'bouncedRecipients => [ {
116
- # 'emailAddress' => 'bounce@simulator.amazonses.com',
117
- # 'action' => 'failed',
118
- # 'status' => '5.1.1',
119
- # 'diagnosticCode' => 'smtp; 550 5.1.1 user unknown'
120
- # }, ... ]
121
- v['action'] = e['action']
122
- v['status'] = e['status']
123
-
124
- if p0 = e['diagnosticCode'].index(';') || -1; p0 > 3
125
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
126
- v['spec'] = e['diagnosticCode'][0, p0].upcase
127
- v['diagnosis'] = e['diagnosticCode'][p0 + 2, e['diagnosticCode'].size]
128
- else
129
- v['diagnosis'] = e['diagnosticCode']
130
- end
131
-
132
- # 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
133
- p0 = o['reportingMTA'].index('dsn; ')
134
- v['lhost'] = Sisimai::String.sweep(o['reportingMTA'][p0 + 5, o['reportingMTA'].size]) if p0
135
-
136
- if bouncetype[o['bounceType']] && bouncetype[o['bounceType']][o['bounceSubType']]
137
- # 'bounce' => {
138
- # 'bounceType' => 'Permanent',
139
- # 'bounceSubType' => 'General'
140
- # },
141
- v['reason'] = bouncetype[o['bounceType']][o['bounceSubType']]
142
- end
143
- else
144
- # 'complainedRecipients' => [ {
145
- # 'emailAddress' => 'complaint@simulator.amazonses.com' }, ... ],
146
- v['reason'] = 'feedback'
147
- v['feedbacktype'] = o['complaintFeedbackType'] || ''
148
- end
149
-
150
- v['date'] = o['timestamp'] || p['mail']['timestamp']
151
- v['date'].sub!(/[.]\d+Z\z/, '')
152
- end
153
- elsif p['notificationType'] == 'Delivery'
154
- # { "notificationType":"Delivery", "delivery": { ...
155
- o = p['delivery'].dup
156
- r = o['recipients'] || []
157
-
158
- while e = r.shift do
159
- # 'delivery' => {
160
- # 'timestamp' => '2016-11-23T12:01:03.512Z',
161
- # 'processingTimeMillis' => 3982,
162
- # 'reportingMTA' => 'a27-29.smtp-out.us-west-2.amazonses.com',
163
- # 'recipients' => [
164
- # 'success@simulator.amazonses.com'
165
- # ],
166
- # 'smtpResponse' => '250 2.6.0 Message received'
167
- # },
168
- next unless Sisimai::Address.is_emailaddress(e)
169
-
170
- v = dscontents[-1]
171
- if v['recipient']
172
- # There are multiple recipient addresses in the message body.
173
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
174
- v = dscontents[-1]
175
- end
176
- recipients += 1
177
- v['recipient'] = e
178
- v['lhost'] = o['reportingMTA'] || ''
179
- v['diagnosis'] = o['smtpResponse'] || ''
180
- v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || ''
181
- v['reason'] = 'delivered'
182
- v['action'] = 'delivered'
183
-
184
- v['date'] = o['timestamp'] || p['mail']['timestamp']
185
- v['date'].sub!(/[.]\d+Z\z/, '')
186
- end
94
+ break if sespayload.include?("notificationType") == false
95
+ break if sespayload.start_with?("{") == false
96
+ break if sespayload.end_with?("}") == false
97
+ proceedsto = true; break
98
+ end
99
+ return nil if proceedsto == false
100
+
101
+ jsonobject = nil
102
+ begin
103
+ if RUBY_PLATFORM.start_with?("java")
104
+ # java-based ruby environment like JRuby.
105
+ require "jrjackson"
106
+ jsonobject = JrJackson::Json.load(sespayload)
187
107
  else
188
- # The value of "notificationType" is not any of "Bounce", "Complaint", or "Delivery".
189
- return nil
108
+ # Matz' Ruby Implementation
109
+ require "oj"
110
+ jsonobject = Oj.load(sespayload)
190
111
  end
191
- return nil unless recipients > 0
192
-
193
- if p['mail']['headers']
194
- # "headersTruncated":false,
195
- # "headers":[ { ...
196
- p['mail']['headers'].each do |e|
197
- # 'headers' => [ { 'name' => 'From', 'value' => 'neko@nyaan.jp' }, ... ],
198
- next unless %w[From To Subject Message-ID Date].index(e['name'])
199
- rfc822head[e['name'].downcase] = e['value']
112
+ rescue StandardError => ce
113
+ # Something wrong in decoding JSON
114
+ warn ' ***warning: Failed to decode JSON: ' << ce.to_s
115
+ return nil
116
+ end
117
+ return nil if jsonobject.has_key?("notificationType") == false
118
+
119
+ require "sisimai/string"
120
+ require "sisimai/rfc1123"
121
+ require "sisimai/smtp/reply"
122
+ require "sisimai/smtp/status"
123
+ require "sisimai/smtp/command"
124
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
125
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
126
+ whatnotify = jsonobject["notificationType"][0, 1] || ""
127
+ v = dscontents[-1]
128
+
129
+ if whatnotify == "B"
130
+ # "notificationType":"Bounce"
131
+ p = jsonobject["bounce"]
132
+ r = p["bounceType"] == "Permanent" ? "5" : "4"
133
+
134
+ p["bouncedRecipients"].each do |e|
135
+ # {"emailAddress":"neko@example.jp", "action":"failed", "status":"5.1.1", "diagnosticCode": "..."}
136
+ if v["recipient"] != ""
137
+ # There are multiple recipient addresses in the message body.
138
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
139
+ v = dscontents[-1]
200
140
  end
141
+ v["recipient"] = e["emailAddress"]
142
+ v["diagnosis"] = Sisimai::String.sweep(e["diagnosticCode"])
143
+ v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
144
+ v["action"] = e["action"]
145
+ v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], r)
146
+ v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], v["status"])
147
+ v["date"] = p["timestamp"]
148
+ v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
149
+ recipients += 1
201
150
  end
202
151
 
203
- unless rfc822head['message-id']
204
- # Try to get the value of "Message-Id".
205
- if p['mail']['messageId']
206
- # 'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
207
- rfc822head['message-id'] = p['mail']['messageId']
208
- end
152
+ ReasonPair.each_key do |f|
153
+ # Try to find the bounce reason by "bounceSubType"
154
+ next unless ReasonPair[f] == p["bounceSubType"]
155
+ v["reason"] = f; break
209
156
  end
210
- return { 'ds' => dscontents, 'rfc822' => rfc822head }
211
157
 
212
- else
213
- # The message body is an email
214
- # :from => %r/\AMAILER-DAEMON[@]email[-]bounces[.]amazonses[.]com\z/,
215
- # :subject => %r/\ADelivery Status Notification [(]Failure[)]\z/,
216
- return nil if mhead['x-mailer'].to_s.start_with?('Amazon WorkMail')
217
-
218
- # X-SenderID: Sendmail Sender-ID Filter v1.0.0 nijo.example.jp p7V3i843003008
219
- # X-Original-To: 000001321defbd2a-788e31c8-2be1-422f-a8d4-cf7765cc9ed7-000000@email-bounces.amazonses.com
220
- # X-AWS-Outgoing: 199.255.192.156
221
- # X-SES-Outgoing: 2016.10.12-54.240.27.6
222
- match = 0
223
- match += 1 if mhead['x-aws-outgoing']
224
- match += 1 if mhead['x-ses-outgoing']
225
- return nil unless match > 0
226
-
227
- require 'sisimai/rfc1894'
228
- fieldtable = Sisimai::RFC1894.FIELDTABLE
229
- permessage = {} # (Hash) Store values of each Per-Message field
230
-
231
- emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
232
- bodyslices = emailparts[0].split("\n")
233
- readslices = ['']
234
- readcursor = 0 # (Integer) Points the current cursor position
235
- v = nil
236
-
237
- while e = bodyslices.shift do
238
- # Read error messages and delivery status lines from the head of the email to the previous
239
- # line of the beginning of the original message.
240
- readslices << e # Save the current line for the next loop
241
-
242
- if readcursor == 0
243
- # Beginning of the bounce message or message/delivery-status part
244
- if e.start_with?(StartingOf[:message][0], StartingOf[:message][1])
245
- readcursor |= Indicators[:deliverystatus]
246
- next
247
- end
248
- end
249
- next if (readcursor & Indicators[:deliverystatus]) == 0
250
- next if e.empty?
251
-
252
- if f = Sisimai::RFC1894.match(e)
253
- # "e" matched with any field defined in RFC3464
254
- next unless o = Sisimai::RFC1894.field(e)
158
+ elsif whatnotify == "C"
159
+ # "notificationType":"Complaint"
160
+ p = jsonobject["complaint"]
161
+ p["complainedRecipients"].each do |e|
162
+ # {"emailAddress":"neko@example.jp"}
163
+ if v["recipient"] != ""
164
+ # There are multiple recipient addresses in the message body.
165
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
255
166
  v = dscontents[-1]
256
-
257
- if o[-1] == 'addr'
258
- # Final-Recipient: rfc822; kijitora@example.jp
259
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
260
- if o[0] == 'final-recipient'
261
- # Final-Recipient: rfc822; kijitora@example.jp
262
- if v['recipient']
263
- # There are multiple recipient addresses in the message body.
264
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
265
- v = dscontents[-1]
266
- end
267
- v['recipient'] = o[2]
268
- recipients += 1
269
- else
270
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
271
- v['alias'] = o[2]
272
- end
273
- elsif o[-1] == 'code'
274
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
275
- v['spec'] = o[1]
276
- v['diagnosis'] = o[2]
277
- else
278
- # Other DSN fields defined in RFC3464
279
- next unless fieldtable[o[0]]
280
- v[fieldtable[o[0]]] = o[2]
281
-
282
- next unless f
283
- permessage[fieldtable[o[0]]] = o[2]
284
- end
285
- else
286
- # Continued line of the value of Diagnostic-Code field
287
- next unless readslices[-2].start_with?('Diagnostic-Code:')
288
- next unless e.start_with?(' ')
289
- v['diagnosis'] << ' ' << e
290
- readslices[-1] = 'Diagnostic-Code: ' << e
291
- end
292
- end
293
-
294
- if recipients == 0 && mbody =~ /notificationType/
295
- # Try to parse with Sisimai::Lhost::AmazonSES module
296
- j = Sisimai::Lhost::AmazonSES.json(mhead, mbody)
297
-
298
- if j['ds'].is_a? Array
299
- # Update dscontents
300
- dscontents = j['ds']
301
- recipients = j['ds'].size
302
167
  end
168
+ v["recipient"] = e["emailAddress"]
169
+ v["reason"] = "feedback"
170
+ v["feedbacktype"] = p["complaintFeedbackType"]
171
+ v["date"] = p["timestamp"]
172
+ v["diagnosis"] = sprintf('"feedbackid":"%s", "useragent":"%s"}', p["feedbackId"], p["userAgent"])
173
+ recipients += 1
303
174
  end
304
- return nil unless recipients > 0
305
175
 
306
- dscontents.each do |e|
307
- # Set default values if each value is empty.
308
- permessage.each_key { |a| e[a] ||= permessage[a] || '' }
309
-
310
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].to_s.tr("\n", ' '))
311
- if e['status'].to_s.end_with?('.0.0', '.1.0')
312
- # Get other D.S.N. value from the error message
313
- # 5.1.0 - Unknown address error 550-'5.7.1 ...
314
- errormessage = e['diagnosis']
315
- p1 = e['diagnosis'].index("-'"); p1 = e['diagnosis'].index('-"') unless p1
316
- p2 = e['diagnosis'].rindex("' "); p2 = e['diagnosis'].rindex('" ') unless p2
317
- errormessage = e['diagnosis'][p1 + 2, p2 - p1 - 2] if p1 && p2
318
- e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status']
319
- end
320
- e['replycode'] ||= Sisimai::SMTP::Reply.find(e['diagnosis'], e['status'])
321
-
322
- MessagesOf.each_key do |r|
323
- # Verify each regular expression of session errors
324
- next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) }
325
- e['reason'] = r
326
- break
176
+ elsif whatnotify == "D"
177
+ # "notificationType":"Delivery"
178
+ p = jsonobject["delivery"]
179
+ p["recipients"].each do |e|
180
+ # {"recipients":["neko@example.jp"]}
181
+ if v["recipient"] != ""
182
+ # There are multiple recipient addresses in the message body.
183
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
184
+ v = dscontents[-1]
327
185
  end
186
+ v["recipient"] = e
187
+ v["reason"] = "delivered"
188
+ v["action"] = "delivered"
189
+ v["date"] = p["timestamp"]
190
+ v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
191
+ v["diagnosis"] = Sisimai::String.sweep(p["smtpResponse"])
192
+ v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
193
+ v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], "2")
194
+ v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], "2")
195
+ recipients += 1
328
196
  end
329
197
 
330
- return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
331
- end # END of a parser for email message
332
-
198
+ else
199
+ # Unknown "notificationType" value
200
+ warn sprintf(" ***warning: There is no notificationType field or unknown type of notificationType field")
201
+ return nil
202
+ end
203
+ return nil if recipients == 0
204
+
205
+ # Date::Time.strptime() cannot parse "2016-11-25T01:49:01.000Z" format
206
+ dscontents.each { |e| e["date"] = e["date"].sub("T", " ").sub(/[.]\d{3}Z/, "") }
207
+
208
+ cv = ""
209
+ jsonobject["mail"]["headers"].each do |e|
210
+ cv << sprintf("%s: %s\n", e["name"], e["value"])
211
+ end
212
+ %w[date subject].each do |e|
213
+ next if jsonobject["mail"]["commonHeaders"].has_key?(e) == false
214
+ cv << sprintf("%s: %s\n", e.capitalize, jsonobject["mail"]["commonHeaders"][e])
215
+ end
216
+ return { "ds" => dscontents, "rfc822" => cv }
333
217
  end
334
218
  def description; return 'Amazon SES(Sending): https://aws.amazon.com/ses/'; end
335
219
  end