@growth-labs/mailer 0.8.0 → 0.8.1
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/dist/utils/providers.js +79 -2
- package/dist/utils/providers.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/providers.ts +75 -2
package/dist/utils/providers.js
CHANGED
|
@@ -24,7 +24,7 @@ export class CloudflareEmailProvider {
|
|
|
24
24
|
}
|
|
25
25
|
catch (err) {
|
|
26
26
|
const message = err instanceof Error ? err.message : String(err);
|
|
27
|
-
const isRetryable =
|
|
27
|
+
const isRetryable = isRetryableFailure(err, message);
|
|
28
28
|
const errorCode = providerErrorCode(err);
|
|
29
29
|
return {
|
|
30
30
|
success: false,
|
|
@@ -54,18 +54,95 @@ function providerErrorCode(err) {
|
|
|
54
54
|
return String(code);
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Whether a failed send is worth retrying (`soft`) rather than a permanent refusal (`hard`).
|
|
59
|
+
*
|
|
60
|
+
* A status code decides before any wording does:
|
|
61
|
+
* - an SMTP enhanced status (`5.1.1`) or reply code (`550`) in the error's code fields or at the
|
|
62
|
+
* start of its message: `5xx` is permanent, `4xx` is transient;
|
|
63
|
+
* - an HTTP status from the provider's API (`status` / `statusCode`): `5xx`, `408`, `425` and
|
|
64
|
+
* `429` are transient, any other `4xx` is permanent.
|
|
65
|
+
* Without a code, network faults, timeouts and server-side wording are transient and everything
|
|
66
|
+
* else is permanent. The message is never scanned for a bare digit.
|
|
67
|
+
*/
|
|
68
|
+
function isRetryableFailure(err, message) {
|
|
69
|
+
const smtp = smtpClass(err, message);
|
|
70
|
+
if (smtp !== undefined)
|
|
71
|
+
return smtp === 4;
|
|
72
|
+
const http = httpStatus(err);
|
|
73
|
+
if (http !== undefined)
|
|
74
|
+
return http >= 500 || http === 408 || http === 425 || http === 429;
|
|
75
|
+
return isNetworkOrServerError(message);
|
|
76
|
+
}
|
|
77
|
+
const ENHANCED_STATUS = /(?<![\d.])([245])\.\d{1,3}\.\d{1,3}(?![\d.])/;
|
|
78
|
+
const SMTP_REPLY_AT_START = /^\s*([45])\d\d(?:[\s-]|$)/;
|
|
79
|
+
const SMTP_REPLY_LABELLED = /\b(?:smtp|esmtp)\b[^\d]{0,24}([45])\d\d(?!\d)|\b(?:reply|response)\s+code[:\s]*([45])\d\d(?!\d)/i;
|
|
80
|
+
/** The SMTP status class (4 or 5) an error reports, or undefined when it reports none. */
|
|
81
|
+
function smtpClass(err, message) {
|
|
82
|
+
const fields = [];
|
|
83
|
+
if (typeof err === 'object' && err !== null) {
|
|
84
|
+
const e = err;
|
|
85
|
+
fields.push(e.enhancedStatus, e.enhancedCode, e.responseCode, e.smtpCode, e.smtpStatus);
|
|
86
|
+
// A numeric `code` (or a bare "550") is an SMTP reply code; other string codes are provider codes.
|
|
87
|
+
if (typeof e.code === 'number')
|
|
88
|
+
fields.push(e.code);
|
|
89
|
+
else if (typeof e.code === 'string' && /^[45]\d\d$/.test(e.code))
|
|
90
|
+
fields.push(Number(e.code));
|
|
91
|
+
}
|
|
92
|
+
for (const f of fields) {
|
|
93
|
+
if (typeof f === 'number' && Number.isInteger(f) && f >= 400 && f < 600) {
|
|
94
|
+
return f >= 500 ? 5 : 4;
|
|
95
|
+
}
|
|
96
|
+
if (typeof f === 'string') {
|
|
97
|
+
const enhanced = ENHANCED_STATUS.exec(f);
|
|
98
|
+
if (enhanced)
|
|
99
|
+
return enhanced[1] === '5' ? 5 : enhanced[1] === '4' ? 4 : undefined;
|
|
100
|
+
const reply = SMTP_REPLY_AT_START.exec(f);
|
|
101
|
+
if (reply)
|
|
102
|
+
return reply[1] === '5' ? 5 : 4;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const enhanced = ENHANCED_STATUS.exec(message);
|
|
106
|
+
if (enhanced)
|
|
107
|
+
return enhanced[1] === '5' ? 5 : enhanced[1] === '4' ? 4 : undefined;
|
|
108
|
+
const reply = SMTP_REPLY_AT_START.exec(message) ?? SMTP_REPLY_LABELLED.exec(message);
|
|
109
|
+
if (reply) {
|
|
110
|
+
const digit = reply[1] ?? reply[2];
|
|
111
|
+
return digit === '5' ? 5 : 4;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** The HTTP status the provider's API answered with, when the error carries one. */
|
|
116
|
+
function httpStatus(err) {
|
|
117
|
+
if (typeof err !== 'object' || err === null)
|
|
118
|
+
return undefined;
|
|
119
|
+
const e = err;
|
|
120
|
+
for (const v of [e.status, e.statusCode]) {
|
|
121
|
+
if (typeof v === 'number' && Number.isInteger(v) && v >= 400 && v < 600)
|
|
122
|
+
return v;
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
57
126
|
function isNetworkOrServerError(message) {
|
|
58
127
|
const patterns = [
|
|
59
128
|
'network',
|
|
60
129
|
'timeout',
|
|
130
|
+
'timed out',
|
|
131
|
+
'etimedout',
|
|
61
132
|
'econnrefused',
|
|
62
133
|
'econnreset',
|
|
63
134
|
'enotfound',
|
|
135
|
+
'eai_again',
|
|
136
|
+
'epipe',
|
|
137
|
+
'ehostunreach',
|
|
138
|
+
'fetch failed',
|
|
64
139
|
'socket',
|
|
65
|
-
'5',
|
|
66
140
|
'server error',
|
|
67
141
|
'service unavailable',
|
|
68
142
|
'internal server',
|
|
143
|
+
'temporarily',
|
|
144
|
+
'try again',
|
|
145
|
+
'rate limit',
|
|
69
146
|
];
|
|
70
147
|
const lower = message.toLowerCase();
|
|
71
148
|
return patterns.some((p) => lower.includes(p));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/utils/providers.ts"],"names":[],"mappings":"AAeA,kCAAkC;AAElC,MAAM,OAAO,uBAAuB;IAC1B,IAAI,GAAG,YAAY,CAAA;IACX,MAAM,CAAuB;IAE9C,YAAY,WAAkC;QAC7C,IAAI,CAAC,MAAM,GAAG,WAAW,CAAA;IAC1B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAoB;QAC9B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAiD;gBAC7D,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;aAChB,CAAA;YACD,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;YAChC,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC5D,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAA;YAChC,CAAC;YAED,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAE/B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YAChE,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/utils/providers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/utils/providers.ts
CHANGED
|
@@ -43,7 +43,7 @@ export class CloudflareEmailProvider implements EmailProvider {
|
|
|
43
43
|
return { success: true, retryable: false }
|
|
44
44
|
} catch (err) {
|
|
45
45
|
const message = err instanceof Error ? err.message : String(err)
|
|
46
|
-
const isRetryable =
|
|
46
|
+
const isRetryable = isRetryableFailure(err, message)
|
|
47
47
|
|
|
48
48
|
const errorCode = providerErrorCode(err)
|
|
49
49
|
return {
|
|
@@ -77,18 +77,91 @@ function providerErrorCode(err: unknown): string | undefined {
|
|
|
77
77
|
return undefined
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Whether a failed send is worth retrying (`soft`) rather than a permanent refusal (`hard`).
|
|
82
|
+
*
|
|
83
|
+
* A status code decides before any wording does:
|
|
84
|
+
* - an SMTP enhanced status (`5.1.1`) or reply code (`550`) in the error's code fields or at the
|
|
85
|
+
* start of its message: `5xx` is permanent, `4xx` is transient;
|
|
86
|
+
* - an HTTP status from the provider's API (`status` / `statusCode`): `5xx`, `408`, `425` and
|
|
87
|
+
* `429` are transient, any other `4xx` is permanent.
|
|
88
|
+
* Without a code, network faults, timeouts and server-side wording are transient and everything
|
|
89
|
+
* else is permanent. The message is never scanned for a bare digit.
|
|
90
|
+
*/
|
|
91
|
+
function isRetryableFailure(err: unknown, message: string): boolean {
|
|
92
|
+
const smtp = smtpClass(err, message)
|
|
93
|
+
if (smtp !== undefined) return smtp === 4
|
|
94
|
+
const http = httpStatus(err)
|
|
95
|
+
if (http !== undefined) return http >= 500 || http === 408 || http === 425 || http === 429
|
|
96
|
+
return isNetworkOrServerError(message)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const ENHANCED_STATUS = /(?<![\d.])([245])\.\d{1,3}\.\d{1,3}(?![\d.])/
|
|
100
|
+
const SMTP_REPLY_AT_START = /^\s*([45])\d\d(?:[\s-]|$)/
|
|
101
|
+
const SMTP_REPLY_LABELLED =
|
|
102
|
+
/\b(?:smtp|esmtp)\b[^\d]{0,24}([45])\d\d(?!\d)|\b(?:reply|response)\s+code[:\s]*([45])\d\d(?!\d)/i
|
|
103
|
+
|
|
104
|
+
/** The SMTP status class (4 or 5) an error reports, or undefined when it reports none. */
|
|
105
|
+
function smtpClass(err: unknown, message: string): 4 | 5 | undefined {
|
|
106
|
+
const fields: unknown[] = []
|
|
107
|
+
if (typeof err === 'object' && err !== null) {
|
|
108
|
+
const e = err as Record<string, unknown>
|
|
109
|
+
fields.push(e.enhancedStatus, e.enhancedCode, e.responseCode, e.smtpCode, e.smtpStatus)
|
|
110
|
+
// A numeric `code` (or a bare "550") is an SMTP reply code; other string codes are provider codes.
|
|
111
|
+
if (typeof e.code === 'number') fields.push(e.code)
|
|
112
|
+
else if (typeof e.code === 'string' && /^[45]\d\d$/.test(e.code)) fields.push(Number(e.code))
|
|
113
|
+
}
|
|
114
|
+
for (const f of fields) {
|
|
115
|
+
if (typeof f === 'number' && Number.isInteger(f) && f >= 400 && f < 600) {
|
|
116
|
+
return f >= 500 ? 5 : 4
|
|
117
|
+
}
|
|
118
|
+
if (typeof f === 'string') {
|
|
119
|
+
const enhanced = ENHANCED_STATUS.exec(f)
|
|
120
|
+
if (enhanced) return enhanced[1] === '5' ? 5 : enhanced[1] === '4' ? 4 : undefined
|
|
121
|
+
const reply = SMTP_REPLY_AT_START.exec(f)
|
|
122
|
+
if (reply) return reply[1] === '5' ? 5 : 4
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const enhanced = ENHANCED_STATUS.exec(message)
|
|
126
|
+
if (enhanced) return enhanced[1] === '5' ? 5 : enhanced[1] === '4' ? 4 : undefined
|
|
127
|
+
const reply = SMTP_REPLY_AT_START.exec(message) ?? SMTP_REPLY_LABELLED.exec(message)
|
|
128
|
+
if (reply) {
|
|
129
|
+
const digit = reply[1] ?? reply[2]
|
|
130
|
+
return digit === '5' ? 5 : 4
|
|
131
|
+
}
|
|
132
|
+
return undefined
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The HTTP status the provider's API answered with, when the error carries one. */
|
|
136
|
+
function httpStatus(err: unknown): number | undefined {
|
|
137
|
+
if (typeof err !== 'object' || err === null) return undefined
|
|
138
|
+
const e = err as { status?: unknown; statusCode?: unknown }
|
|
139
|
+
for (const v of [e.status, e.statusCode]) {
|
|
140
|
+
if (typeof v === 'number' && Number.isInteger(v) && v >= 400 && v < 600) return v
|
|
141
|
+
}
|
|
142
|
+
return undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
80
145
|
function isNetworkOrServerError(message: string): boolean {
|
|
81
146
|
const patterns = [
|
|
82
147
|
'network',
|
|
83
148
|
'timeout',
|
|
149
|
+
'timed out',
|
|
150
|
+
'etimedout',
|
|
84
151
|
'econnrefused',
|
|
85
152
|
'econnreset',
|
|
86
153
|
'enotfound',
|
|
154
|
+
'eai_again',
|
|
155
|
+
'epipe',
|
|
156
|
+
'ehostunreach',
|
|
157
|
+
'fetch failed',
|
|
87
158
|
'socket',
|
|
88
|
-
'5',
|
|
89
159
|
'server error',
|
|
90
160
|
'service unavailable',
|
|
91
161
|
'internal server',
|
|
162
|
+
'temporarily',
|
|
163
|
+
'try again',
|
|
164
|
+
'rate limit',
|
|
92
165
|
]
|
|
93
166
|
const lower = message.toLowerCase()
|
|
94
167
|
return patterns.some((p) => lower.includes(p))
|