email-bounce-parser 1.0.15 → 1.0.17
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.
- package/README.md +1 -1
- package/lib/parser.js +74 -16
- package/lib/utils.js +5 -6
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ console.log(result);
|
|
|
39
39
|
### Parse a bounce email
|
|
40
40
|
|
|
41
41
|
`read(body)` parses the bounce email and extracts available information (error code and type, server, original recipient, etc.):
|
|
42
|
-
* `body` must be a string representing the bounce email body (
|
|
42
|
+
* `body` must be a string representing the bounce email **text** body (not HTML body). You can use [mailparser](https://github.com/nodemailer/mailparser) for example.
|
|
43
43
|
|
|
44
44
|
```js
|
|
45
45
|
const EmailBounceParse = require("email-bounce-parser");
|
package/lib/parser.js
CHANGED
|
@@ -8,6 +8,7 @@ var ERROR_CODES_TYPES = {
|
|
|
8
8
|
"421" : "service_not_available",
|
|
9
9
|
"421 4.3.0" : "temporary_system_problem",
|
|
10
10
|
"421 4.4.5" : "server_busy",
|
|
11
|
+
"421 4.7.28" : "ip_blocked_spam",
|
|
11
12
|
"432 4.7.12" : "password_needed",
|
|
12
13
|
"450" : "action_not_taken_mailbox_unavailable",
|
|
13
14
|
"450 4.2.1" : "action_not_taken_mailbox_rate_limited",
|
|
@@ -64,8 +65,8 @@ var REGEXES = {
|
|
|
64
65
|
/\(in reply to end of ([A-Z\s]+) command\)$/m, // "in reply to end of DATA command"
|
|
65
66
|
/\(in reply to ([A-Z\s]+) command\)$/m, // "in reply to RCPT TO command"
|
|
66
67
|
/timed out while sending ([A-Z\s]+)$/m, // "timed out while sending (RCPT TO)", "timed out while sending RCPT TO"
|
|
67
|
-
/error after ([A-Z\s]+)/
|
|
68
|
-
/while sending end of ([a-z\s]+)/
|
|
68
|
+
/error after ([A-Z\s]+)/i, // "LMTP error after RCPT TO"
|
|
69
|
+
/while sending end of ([a-z\s]+)/i // "while sending end of data"
|
|
69
70
|
],
|
|
70
71
|
|
|
71
72
|
email_server : [
|
|
@@ -84,7 +85,7 @@ var REGEXES = {
|
|
|
84
85
|
/said\: (\d{3}(?:[\s\-]\d\.\d\.\d\d?)?) (.+) \(in reply to/,
|
|
85
86
|
/said\: (\d{3}(?:[\s\-]\d\.\d\.\d\d?)?)-(.+)/,
|
|
86
87
|
/(\d{3}\-\'\d\.\d\.\d\d?) (.+)/,
|
|
87
|
-
/LMTP error after
|
|
88
|
+
/LMTP error after .+?\:\<.+?\>\: (\d{3}(?:[\s\-]\d\.\d\.\d\d?)?) <.+?\>(.+)$/m,
|
|
88
89
|
/remote server returned \'(\d{3}(?:[\s\-]\d\.\d\.\d\d?)?) (.+)\'/i,
|
|
89
90
|
/The response from the remote server was:\s(\d{3}(?:[\s\-]\d\.\d\.\d\d?)?) (.+)/,
|
|
90
91
|
/refused to talk to me\: (\d{3}(?:[\s\-]\d\.\d\.\d\d?)?) (.+)/,
|
|
@@ -94,18 +95,29 @@ var REGEXES = {
|
|
|
94
95
|
],
|
|
95
96
|
|
|
96
97
|
error_lax : [
|
|
97
|
-
|
|
98
|
+
/^(?:\<.+?\> \(expanded from \<.+?\>\)\:)(.+)/m, // "<john@acme.com> (expanded from <john@digiflex.com>): Domain name not found"
|
|
99
|
+
/^(?:\<.+?\>\:)(.+)/m, // "<john@acme.com>: Domain name not found"
|
|
100
|
+
],
|
|
101
|
+
|
|
102
|
+
error_label_cleanup_recipient : [
|
|
103
|
+
/^\<.+?\> \(expanded from \<.+?\>\)\:/m, // "<john@acme.com> (expanded from <john@digiflex.com>): Domain name not found"
|
|
104
|
+
/^\<.+?\>\:/m, // "<john@acme.com>: Domain name not found"
|
|
105
|
+
],
|
|
106
|
+
|
|
107
|
+
error_label_cleanup_command : [
|
|
108
|
+
/\(in reply to (?:end of)?[A-Z\s]+ command\)$/m // "in reply to DATA command" or "in reply to end of DATA command"
|
|
98
109
|
],
|
|
99
110
|
|
|
100
111
|
recipient : [
|
|
101
|
-
/
|
|
102
|
-
/
|
|
103
|
-
/to the following addresses:\s*(.+)
|
|
112
|
+
/the following addresses had permanent fatal errors (?:.*?)\s\<(.+?)\>/i,
|
|
113
|
+
/the following message to \<(.+?)\> was undeliverable/i,
|
|
114
|
+
/to the following addresses:\s*(.+)/i,
|
|
104
115
|
/^\<(.+?)\>:/m,
|
|
105
|
-
/^(.+?)\s?\n?\s*[\[|<]mailto\:(.+?)[\]|>]/
|
|
106
|
-
/^
|
|
107
|
-
/^
|
|
108
|
-
/\<(.+?)
|
|
116
|
+
/^(.+?)\s?\n?\s*[\[|<]mailto\:(.+?)[\]|>]/mi, // "john@acme.com<mailto:john@acme.com>"
|
|
117
|
+
/^your message wasn't delivered to (.+?)\s/mi, // "Your message wasn't delivered to john@acme.com"
|
|
118
|
+
/^your message to (.+?) couldn\'t/mi, // "Your message to john@acme.com couldn't"
|
|
119
|
+
/\<(.+?)\> \(expanded from \<.+?\>\)\:/, // "<john@acme.com> (expanded from <john@digiflex.com>): "
|
|
120
|
+
/\<(.+?)\>:/ // "<john@acme.com>:"
|
|
109
121
|
],
|
|
110
122
|
|
|
111
123
|
temporary_safe : /(temporary|temporarily)/i,
|
|
@@ -122,7 +134,9 @@ var REGEXES = {
|
|
|
122
134
|
],
|
|
123
135
|
|
|
124
136
|
enhanced_types_server_unknown : [
|
|
125
|
-
/(does not accept mail)/i
|
|
137
|
+
/(does not accept mail)/i,
|
|
138
|
+
/(domain name not found)/i,
|
|
139
|
+
/(no route to host)/i
|
|
126
140
|
],
|
|
127
141
|
|
|
128
142
|
enhanced_types_server_timeout : [
|
|
@@ -134,8 +148,8 @@ var REGEXES = {
|
|
|
134
148
|
],
|
|
135
149
|
|
|
136
150
|
error_code : [
|
|
137
|
-
/(\d{3})[\s\-](\d\.\d\.\d)/, // "550 5.4.1" or "550-5.4.1"
|
|
138
|
-
/(\d{3})\-\'(\d\.\d\.\d)/, // "550-'5.4.1"
|
|
151
|
+
/(\d{3})[\s\-](\d\.\d\.\d\d?)/, // "550 5.4.1" or "550-5.4.1" or "550-5.4.11"
|
|
152
|
+
/(\d{3})\-\'(\d\.\d\.\d\d?)/, // "550-'5.4.1" or "550-'5.4.11"
|
|
139
153
|
/(\d{3})/ // "550"
|
|
140
154
|
]
|
|
141
155
|
};
|
|
@@ -276,7 +290,7 @@ class Parser {
|
|
|
276
290
|
|
|
277
291
|
if (_match && _match.length > 1) {
|
|
278
292
|
_code = this.__parseErrorCode(_match[1]);
|
|
279
|
-
_label =
|
|
293
|
+
_label = this.__cleanErrorLabel(_match[2]);
|
|
280
294
|
|
|
281
295
|
// Check temporary / permanent
|
|
282
296
|
if (_code.basic[0] === "4") {
|
|
@@ -306,7 +320,7 @@ class Parser {
|
|
|
306
320
|
var _match = loopRegexes(this.__regexes.error_lax, str);
|
|
307
321
|
|
|
308
322
|
if (_match && _match.length > 1) {
|
|
309
|
-
_label =
|
|
323
|
+
_label = this.__cleanErrorLabel(_match[1]);
|
|
310
324
|
}
|
|
311
325
|
}
|
|
312
326
|
|
|
@@ -550,6 +564,50 @@ class Parser {
|
|
|
550
564
|
|
|
551
565
|
return _str;
|
|
552
566
|
}
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Cleans error label
|
|
571
|
+
* @private
|
|
572
|
+
* @param {string} [label]
|
|
573
|
+
* @return {string} Unquoted text
|
|
574
|
+
*/
|
|
575
|
+
__cleanErrorLabel(label="") {
|
|
576
|
+
var _label = this.__unquoteText(label);
|
|
577
|
+
|
|
578
|
+
_label = trimString(_label);
|
|
579
|
+
|
|
580
|
+
// Strip recipient (at the beginning) and command (at the end), in case \
|
|
581
|
+
// they are still present
|
|
582
|
+
_label = loopRegexes(this.__regexes.error_label_cleanup_recipient, _label, "replace");
|
|
583
|
+
_label = loopRegexes(this.__regexes.error_label_cleanup_command, _label, "replace");
|
|
584
|
+
|
|
585
|
+
_label = trimString(_label);
|
|
586
|
+
|
|
587
|
+
return _label;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Unquotes text
|
|
593
|
+
* @private
|
|
594
|
+
* @param {string} [str]
|
|
595
|
+
* @return {string} Unquoted text
|
|
596
|
+
*/
|
|
597
|
+
__unquoteText(str="") {
|
|
598
|
+
var _str = str;
|
|
599
|
+
|
|
600
|
+
_str = trimString(_str);
|
|
601
|
+
|
|
602
|
+
if (
|
|
603
|
+
(_str.startsWith("'") && _str.endsWith("'")) ||
|
|
604
|
+
(_str.startsWith("\"") && _str.endsWith("\""))
|
|
605
|
+
) {
|
|
606
|
+
return _str.slice(1, -1);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return _str;
|
|
610
|
+
}
|
|
553
611
|
}
|
|
554
612
|
|
|
555
613
|
|
package/lib/utils.js
CHANGED
|
@@ -23,6 +23,8 @@ var loopRegexes = function(
|
|
|
23
23
|
regexes, str, mode = "match", highestPosition = true
|
|
24
24
|
) {
|
|
25
25
|
var _match;
|
|
26
|
+
var _minLength = mode === "split" ? 1 : 0;
|
|
27
|
+
var _maxLength = str.length;
|
|
26
28
|
|
|
27
29
|
for (var _i = 0; _i < regexes.length; _i++) {
|
|
28
30
|
var _regex = regexes[_i];
|
|
@@ -40,15 +42,12 @@ var loopRegexes = function(
|
|
|
40
42
|
_match = _currentMatch;
|
|
41
43
|
|
|
42
44
|
// A successfull replace must return a smaller string than the input
|
|
43
|
-
var _maxLength = str.length;
|
|
44
|
-
|
|
45
45
|
if (_match.length < _maxLength) {
|
|
46
46
|
break;
|
|
47
47
|
}
|
|
48
48
|
} else if (mode === "split" || mode === "match") {
|
|
49
|
-
// A successfull split must return an array with at least two items
|
|
50
|
-
var _minLength = mode === "split" ? 1 : 0;
|
|
51
49
|
|
|
50
|
+
// A successfull split must return an array with at least two items
|
|
52
51
|
if ((_currentMatch || []).length > _minLength) {
|
|
53
52
|
if (highestPosition === true) {
|
|
54
53
|
// No previous match?
|
|
@@ -80,7 +79,7 @@ var loopRegexes = function(
|
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
return (_match || []);
|
|
82
|
+
return (mode === "replace") ? (_match || "") : (_match || []);
|
|
84
83
|
}
|
|
85
84
|
|
|
86
85
|
|
|
@@ -91,7 +90,7 @@ var loopRegexes = function(
|
|
|
91
90
|
* @return {string} The trimed string
|
|
92
91
|
*/
|
|
93
92
|
var trimString = function(str) {
|
|
94
|
-
return ((str || "").trim() ||
|
|
93
|
+
return ((str || "").trim() || "");
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "email-bounce-parser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Parses bounce emails and extract errors",
|
|
5
5
|
"author": "Eliott Vincent <eliott@crisp.chat>",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"mail",
|
|
31
31
|
"email",
|
|
32
32
|
"bounce",
|
|
33
|
-
"bounces"
|
|
33
|
+
"bounces",
|
|
34
|
+
"smtp",
|
|
35
|
+
"rfc"
|
|
34
36
|
],
|
|
35
37
|
"license": "MIT"
|
|
36
38
|
}
|