@strapi/provider-email-sendmail 5.45.0 → 5.46.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.
- package/README.md +19 -8
- package/dist/addressing.d.ts +19 -0
- package/dist/addressing.d.ts.map +1 -0
- package/dist/addressing.js +64 -0
- package/dist/addressing.js.map +1 -0
- package/dist/addressing.mjs +58 -0
- package/dist/addressing.mjs.map +1 -0
- package/dist/direct-smtp.d.ts +15 -0
- package/dist/direct-smtp.d.ts.map +1 -0
- package/dist/direct-smtp.js +162 -0
- package/dist/direct-smtp.js.map +1 -0
- package/dist/direct-smtp.mjs +159 -0
- package/dist/direct-smtp.mjs.map +1 -0
- package/dist/index.d.ts +7 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +16 -24
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.ts +15 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +36 -0
- package/dist/logger.js.map +1 -0
- package/dist/logger.mjs +34 -0
- package/dist/logger.mjs.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -23,14 +23,25 @@ npm install @strapi/provider-email-sendmail --save
|
|
|
23
23
|
|
|
24
24
|
## Configuration
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
| settings
|
|
33
|
-
|
|
|
26
|
+
This provider implements the same **direct SMTP delivery** model as the historical `sendmail` npm package (MX lookup per recipient domain, optional DKIM, development mode to target a local SMTP capture server). It uses **Nodemailer** for MIME generation and SMTP transport instead of the unmaintained `sendmail` / `mailcomposer` stack.
|
|
27
|
+
|
|
28
|
+
| Variable | Type | Description | Required | Default |
|
|
29
|
+
| ------------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------- |
|
|
30
|
+
| provider | string | The name of the provider you use | yes | |
|
|
31
|
+
| providerOptions | object | Options below (same names as the legacy `sendmail` package where applicable). | no | {} |
|
|
32
|
+
| settings | object | Settings | no | {} |
|
|
33
|
+
| settings.defaultFrom | string | Default sender mail address | no | undefined |
|
|
34
|
+
| settings.defaultReplyTo | string \| array | Default address or addresses the receiver is asked to reply to | no | undefined |
|
|
35
|
+
| providerOptions.dkim | object \| boolean | DKIM parameters: `{ privateKey, keySelector? }` (passed to Nodemailer’s DKIM signing). | no | false |
|
|
36
|
+
| providerOptions.silent | boolean | Suppress default console logging from the provider (default merged to `true` unless overridden). | no | (merged) |
|
|
37
|
+
| providerOptions.devPort | number | If set to a **positive** port, delivery targets `devHost:devPort` instead of performing MX DNS (e.g. MailHog / local test SMTP). **The connection uses this port** (same as the legacy package). | no | -1 |
|
|
38
|
+
| providerOptions.devHost | string | Host for development mode (default `localhost`). | no | localhost |
|
|
39
|
+
| providerOptions.smtpPort | number | Outbound SMTP port when **not** in `devPort` mode (default **25**). | no | 25 |
|
|
40
|
+
| providerOptions.smtpHost | string | Extra hostname tried after MX records for each domain (legacy behavior). | no | -1 |
|
|
41
|
+
|
|
42
|
+
MIME is now produced with **Nodemailer** instead of the old `mailcomposer` stack, so the raw message may not match the legacy package byte-for-byte; routing and documented `providerOptions` stay the same.
|
|
43
|
+
|
|
44
|
+
You can pass through **additional Nodemailer mail fields** on each `send()` (for example `headers`, `messageId`, `textEncoding`, `priority`) if you need finer control.
|
|
34
45
|
|
|
35
46
|
> :warning: The Shipper Email (or defaultfrom) may also need to be changed in the `Email Templates` tab on the admin panel for emails to send properly
|
|
36
47
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address parsing and grouping — aligned with the legacy `sendmail` npm package
|
|
3
|
+
* (guileen/node-sendmail) behavior for recipient grouping and domain extraction.
|
|
4
|
+
*/
|
|
5
|
+
/** Extract bare email from `Name <email@domain>` or return trimmed input (linear time, no regex). */
|
|
6
|
+
export declare function extractEmail(address: string): string;
|
|
7
|
+
/** Split address lists (legacy package accepted both string and array). */
|
|
8
|
+
export declare function parseAddressList(addresses: string | string[] | undefined): string[];
|
|
9
|
+
/** Domain part of an email address (after the last `@`). */
|
|
10
|
+
export declare function getHostFromAddress(email: string): string | undefined;
|
|
11
|
+
/** Group recipient addresses by recipient domain (MX routing key). */
|
|
12
|
+
export declare function groupRecipientsByDomain(recipients: string[]): Record<string, string[]>;
|
|
13
|
+
/** Collect all recipients from to / cc / bcc (same sources as legacy sendmail). */
|
|
14
|
+
export declare function collectRecipients(mail: {
|
|
15
|
+
to?: string | string[];
|
|
16
|
+
cc?: string | string[];
|
|
17
|
+
bcc?: string | string[];
|
|
18
|
+
}): string[];
|
|
19
|
+
//# sourceMappingURL=addressing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressing.d.ts","sourceRoot":"","sources":["../src/addressing.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWpD;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAWnF;AAWD,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED,sEAAsE;AACtE,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAUtF;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB,GAAG,MAAM,EAAE,CAMX"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Address parsing and grouping — aligned with the legacy `sendmail` npm package
|
|
5
|
+
* (guileen/node-sendmail) behavior for recipient grouping and domain extraction.
|
|
6
|
+
*/ /** Extract bare email from `Name <email@domain>` or return trimmed input (linear time, no regex). */ function extractEmail(address) {
|
|
7
|
+
const trimmed = address.trim();
|
|
8
|
+
const open = trimmed.indexOf('<');
|
|
9
|
+
if (open === -1) {
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
const close = trimmed.indexOf('>', open + 1);
|
|
13
|
+
if (close === -1) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
return trimmed.slice(open + 1, close).trim();
|
|
17
|
+
}
|
|
18
|
+
/** Split address lists (legacy package accepted both string and array). */ function parseAddressList(addresses) {
|
|
19
|
+
if (!addresses) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(addresses)) {
|
|
23
|
+
return addresses.map((a)=>extractEmail(String(a))).filter((a)=>a.length > 0);
|
|
24
|
+
}
|
|
25
|
+
return addresses.split(',').map((a)=>extractEmail(a)).filter((a)=>a.length > 0);
|
|
26
|
+
}
|
|
27
|
+
/** Legacy sendmail host extraction regex from guileen/node-sendmail@1.6.1. */ const LEGACY_HOST_REGEX = /[^@]+@([\w\d\-.]+)/;
|
|
28
|
+
/**
|
|
29
|
+
* Do not run the legacy regex on unbounded input (defense in depth; the pattern is not ReDoS-prone,
|
|
30
|
+
* but capping work is cheap and matches RFC 5321 practical address size expectations).
|
|
31
|
+
*/ const MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX = 320;
|
|
32
|
+
/** Domain part of an email address (after the last `@`). */ function getHostFromAddress(email) {
|
|
33
|
+
const normalized = extractEmail(email);
|
|
34
|
+
if (normalized.length > MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
const match = LEGACY_HOST_REGEX.exec(normalized);
|
|
38
|
+
return match?.[1];
|
|
39
|
+
}
|
|
40
|
+
/** Group recipient addresses by recipient domain (MX routing key). */ function groupRecipientsByDomain(recipients) {
|
|
41
|
+
const groups = {};
|
|
42
|
+
for (const raw of recipients){
|
|
43
|
+
const host = String(getHostFromAddress(raw));
|
|
44
|
+
if (!groups[host]) {
|
|
45
|
+
groups[host] = [];
|
|
46
|
+
}
|
|
47
|
+
groups[host].push(raw);
|
|
48
|
+
}
|
|
49
|
+
return groups;
|
|
50
|
+
}
|
|
51
|
+
/** Collect all recipients from to / cc / bcc (same sources as legacy sendmail). */ function collectRecipients(mail) {
|
|
52
|
+
return [
|
|
53
|
+
...parseAddressList(mail.to),
|
|
54
|
+
...parseAddressList(mail.cc),
|
|
55
|
+
...parseAddressList(mail.bcc)
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
exports.collectRecipients = collectRecipients;
|
|
60
|
+
exports.extractEmail = extractEmail;
|
|
61
|
+
exports.getHostFromAddress = getHostFromAddress;
|
|
62
|
+
exports.groupRecipientsByDomain = groupRecipientsByDomain;
|
|
63
|
+
exports.parseAddressList = parseAddressList;
|
|
64
|
+
//# sourceMappingURL=addressing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressing.js","sources":["../src/addressing.ts"],"sourcesContent":["/**\n * Address parsing and grouping — aligned with the legacy `sendmail` npm package\n * (guileen/node-sendmail) behavior for recipient grouping and domain extraction.\n */\n\n/** Extract bare email from `Name <email@domain>` or return trimmed input (linear time, no regex). */\nexport function extractEmail(address: string): string {\n const trimmed = address.trim();\n const open = trimmed.indexOf('<');\n if (open === -1) {\n return trimmed;\n }\n const close = trimmed.indexOf('>', open + 1);\n if (close === -1) {\n return trimmed;\n }\n return trimmed.slice(open + 1, close).trim();\n}\n\n/** Split address lists (legacy package accepted both string and array). */\nexport function parseAddressList(addresses: string | string[] | undefined): string[] {\n if (!addresses) {\n return [];\n }\n if (Array.isArray(addresses)) {\n return addresses.map((a) => extractEmail(String(a))).filter((a) => a.length > 0);\n }\n return addresses\n .split(',')\n .map((a) => extractEmail(a))\n .filter((a) => a.length > 0);\n}\n\n/** Legacy sendmail host extraction regex from guileen/node-sendmail@1.6.1. */\nconst LEGACY_HOST_REGEX = /[^@]+@([\\w\\d\\-.]+)/;\n\n/**\n * Do not run the legacy regex on unbounded input (defense in depth; the pattern is not ReDoS-prone,\n * but capping work is cheap and matches RFC 5321 practical address size expectations).\n */\nconst MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX = 320;\n\n/** Domain part of an email address (after the last `@`). */\nexport function getHostFromAddress(email: string): string | undefined {\n const normalized = extractEmail(email);\n if (normalized.length > MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX) {\n return undefined;\n }\n const match = LEGACY_HOST_REGEX.exec(normalized);\n return match?.[1];\n}\n\n/** Group recipient addresses by recipient domain (MX routing key). */\nexport function groupRecipientsByDomain(recipients: string[]): Record<string, string[]> {\n const groups: Record<string, string[]> = {};\n for (const raw of recipients) {\n const host = String(getHostFromAddress(raw));\n if (!groups[host]) {\n groups[host] = [];\n }\n groups[host].push(raw);\n }\n return groups;\n}\n\n/** Collect all recipients from to / cc / bcc (same sources as legacy sendmail). */\nexport function collectRecipients(mail: {\n to?: string | string[];\n cc?: string | string[];\n bcc?: string | string[];\n}): string[] {\n return [\n ...parseAddressList(mail.to),\n ...parseAddressList(mail.cc),\n ...parseAddressList(mail.bcc),\n ];\n}\n"],"names":["extractEmail","address","trimmed","trim","open","indexOf","close","slice","parseAddressList","addresses","Array","isArray","map","a","String","filter","length","split","LEGACY_HOST_REGEX","MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX","getHostFromAddress","email","normalized","undefined","match","exec","groupRecipientsByDomain","recipients","groups","raw","host","push","collectRecipients","mail","to","cc","bcc"],"mappings":";;AAAA;;;AAGC,0GAGM,SAASA,aAAaC,OAAe,EAAA;IAC1C,MAAMC,OAAAA,GAAUD,QAAQE,IAAI,EAAA;IAC5B,MAAMC,IAAAA,GAAOF,OAAAA,CAAQG,OAAO,CAAC,GAAA,CAAA;IAC7B,IAAID,IAAAA,KAAS,EAAC,EAAG;QACf,OAAOF,OAAAA;AACT,IAAA;AACA,IAAA,MAAMI,KAAAA,GAAQJ,OAAAA,CAAQG,OAAO,CAAC,KAAKD,IAAAA,GAAO,CAAA,CAAA;IAC1C,IAAIE,KAAAA,KAAU,EAAC,EAAG;QAChB,OAAOJ,OAAAA;AACT,IAAA;AACA,IAAA,OAAOA,QAAQK,KAAK,CAACH,IAAAA,GAAO,CAAA,EAAGE,OAAOH,IAAI,EAAA;AAC5C;AAEA,4EACO,SAASK,gBAAAA,CAAiBC,SAAwC,EAAA;AACvE,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA,OAAO,EAAE;AACX,IAAA;IACA,IAAIC,KAAAA,CAAMC,OAAO,CAACF,SAAAA,CAAAA,EAAY;AAC5B,QAAA,OAAOA,SAAAA,CAAUG,GAAG,CAAC,CAACC,IAAMb,YAAAA,CAAac,MAAAA,CAAOD,CAAAA,CAAAA,CAAAA,CAAAA,CAAKE,MAAM,CAAC,CAACF,CAAAA,GAAMA,CAAAA,CAAEG,MAAM,GAAG,CAAA,CAAA;AAChF,IAAA;AACA,IAAA,OAAOP,UACJQ,KAAK,CAAC,GAAA,CAAA,CACNL,GAAG,CAAC,CAACC,CAAAA,GAAMb,YAAAA,CAAaa,CAAAA,CAAAA,CAAAA,CACxBE,MAAM,CAAC,CAACF,CAAAA,GAAMA,CAAAA,CAAEG,MAAM,GAAG,CAAA,CAAA;AAC9B;AAEA,+EACA,MAAME,iBAAAA,GAAoB,oBAAA;AAE1B;;;AAGC,IACD,MAAMC,mCAAAA,GAAsC,GAAA;AAE5C,6DACO,SAASC,kBAAAA,CAAmBC,KAAa,EAAA;AAC9C,IAAA,MAAMC,aAAatB,YAAAA,CAAaqB,KAAAA,CAAAA;IAChC,IAAIC,UAAAA,CAAWN,MAAM,GAAGG,mCAAAA,EAAqC;QAC3D,OAAOI,SAAAA;AACT,IAAA;IACA,MAAMC,KAAAA,GAAQN,iBAAAA,CAAkBO,IAAI,CAACH,UAAAA,CAAAA;IACrC,OAAOE,KAAAA,GAAQ,CAAA,CAAE;AACnB;AAEA,uEACO,SAASE,uBAAAA,CAAwBC,UAAoB,EAAA;AAC1D,IAAA,MAAMC,SAAmC,EAAC;IAC1C,KAAK,MAAMC,OAAOF,UAAAA,CAAY;QAC5B,MAAMG,IAAAA,GAAOhB,OAAOM,kBAAAA,CAAmBS,GAAAA,CAAAA,CAAAA;AACvC,QAAA,IAAI,CAACD,MAAM,CAACE,IAAAA,CAAK,EAAE;YACjBF,MAAM,CAACE,IAAAA,CAAK,GAAG,EAAE;AACnB,QAAA;AACAF,QAAAA,MAAM,CAACE,IAAAA,CAAK,CAACC,IAAI,CAACF,GAAAA,CAAAA;AACpB,IAAA;IACA,OAAOD,MAAAA;AACT;AAEA,oFACO,SAASI,iBAAAA,CAAkBC,IAIjC,EAAA;IACC,OAAO;AACFzB,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKC,EAAE,CAAA;AACxB1B,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKE,EAAE,CAAA;AACxB3B,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKG,GAAG;AAC7B,KAAA;AACH;;;;;;;;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Address parsing and grouping — aligned with the legacy `sendmail` npm package
|
|
3
|
+
* (guileen/node-sendmail) behavior for recipient grouping and domain extraction.
|
|
4
|
+
*/ /** Extract bare email from `Name <email@domain>` or return trimmed input (linear time, no regex). */ function extractEmail(address) {
|
|
5
|
+
const trimmed = address.trim();
|
|
6
|
+
const open = trimmed.indexOf('<');
|
|
7
|
+
if (open === -1) {
|
|
8
|
+
return trimmed;
|
|
9
|
+
}
|
|
10
|
+
const close = trimmed.indexOf('>', open + 1);
|
|
11
|
+
if (close === -1) {
|
|
12
|
+
return trimmed;
|
|
13
|
+
}
|
|
14
|
+
return trimmed.slice(open + 1, close).trim();
|
|
15
|
+
}
|
|
16
|
+
/** Split address lists (legacy package accepted both string and array). */ function parseAddressList(addresses) {
|
|
17
|
+
if (!addresses) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(addresses)) {
|
|
21
|
+
return addresses.map((a)=>extractEmail(String(a))).filter((a)=>a.length > 0);
|
|
22
|
+
}
|
|
23
|
+
return addresses.split(',').map((a)=>extractEmail(a)).filter((a)=>a.length > 0);
|
|
24
|
+
}
|
|
25
|
+
/** Legacy sendmail host extraction regex from guileen/node-sendmail@1.6.1. */ const LEGACY_HOST_REGEX = /[^@]+@([\w\d\-.]+)/;
|
|
26
|
+
/**
|
|
27
|
+
* Do not run the legacy regex on unbounded input (defense in depth; the pattern is not ReDoS-prone,
|
|
28
|
+
* but capping work is cheap and matches RFC 5321 practical address size expectations).
|
|
29
|
+
*/ const MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX = 320;
|
|
30
|
+
/** Domain part of an email address (after the last `@`). */ function getHostFromAddress(email) {
|
|
31
|
+
const normalized = extractEmail(email);
|
|
32
|
+
if (normalized.length > MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const match = LEGACY_HOST_REGEX.exec(normalized);
|
|
36
|
+
return match?.[1];
|
|
37
|
+
}
|
|
38
|
+
/** Group recipient addresses by recipient domain (MX routing key). */ function groupRecipientsByDomain(recipients) {
|
|
39
|
+
const groups = {};
|
|
40
|
+
for (const raw of recipients){
|
|
41
|
+
const host = String(getHostFromAddress(raw));
|
|
42
|
+
if (!groups[host]) {
|
|
43
|
+
groups[host] = [];
|
|
44
|
+
}
|
|
45
|
+
groups[host].push(raw);
|
|
46
|
+
}
|
|
47
|
+
return groups;
|
|
48
|
+
}
|
|
49
|
+
/** Collect all recipients from to / cc / bcc (same sources as legacy sendmail). */ function collectRecipients(mail) {
|
|
50
|
+
return [
|
|
51
|
+
...parseAddressList(mail.to),
|
|
52
|
+
...parseAddressList(mail.cc),
|
|
53
|
+
...parseAddressList(mail.bcc)
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { collectRecipients, extractEmail, getHostFromAddress, groupRecipientsByDomain, parseAddressList };
|
|
58
|
+
//# sourceMappingURL=addressing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addressing.mjs","sources":["../src/addressing.ts"],"sourcesContent":["/**\n * Address parsing and grouping — aligned with the legacy `sendmail` npm package\n * (guileen/node-sendmail) behavior for recipient grouping and domain extraction.\n */\n\n/** Extract bare email from `Name <email@domain>` or return trimmed input (linear time, no regex). */\nexport function extractEmail(address: string): string {\n const trimmed = address.trim();\n const open = trimmed.indexOf('<');\n if (open === -1) {\n return trimmed;\n }\n const close = trimmed.indexOf('>', open + 1);\n if (close === -1) {\n return trimmed;\n }\n return trimmed.slice(open + 1, close).trim();\n}\n\n/** Split address lists (legacy package accepted both string and array). */\nexport function parseAddressList(addresses: string | string[] | undefined): string[] {\n if (!addresses) {\n return [];\n }\n if (Array.isArray(addresses)) {\n return addresses.map((a) => extractEmail(String(a))).filter((a) => a.length > 0);\n }\n return addresses\n .split(',')\n .map((a) => extractEmail(a))\n .filter((a) => a.length > 0);\n}\n\n/** Legacy sendmail host extraction regex from guileen/node-sendmail@1.6.1. */\nconst LEGACY_HOST_REGEX = /[^@]+@([\\w\\d\\-.]+)/;\n\n/**\n * Do not run the legacy regex on unbounded input (defense in depth; the pattern is not ReDoS-prone,\n * but capping work is cheap and matches RFC 5321 practical address size expectations).\n */\nconst MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX = 320;\n\n/** Domain part of an email address (after the last `@`). */\nexport function getHostFromAddress(email: string): string | undefined {\n const normalized = extractEmail(email);\n if (normalized.length > MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX) {\n return undefined;\n }\n const match = LEGACY_HOST_REGEX.exec(normalized);\n return match?.[1];\n}\n\n/** Group recipient addresses by recipient domain (MX routing key). */\nexport function groupRecipientsByDomain(recipients: string[]): Record<string, string[]> {\n const groups: Record<string, string[]> = {};\n for (const raw of recipients) {\n const host = String(getHostFromAddress(raw));\n if (!groups[host]) {\n groups[host] = [];\n }\n groups[host].push(raw);\n }\n return groups;\n}\n\n/** Collect all recipients from to / cc / bcc (same sources as legacy sendmail). */\nexport function collectRecipients(mail: {\n to?: string | string[];\n cc?: string | string[];\n bcc?: string | string[];\n}): string[] {\n return [\n ...parseAddressList(mail.to),\n ...parseAddressList(mail.cc),\n ...parseAddressList(mail.bcc),\n ];\n}\n"],"names":["extractEmail","address","trimmed","trim","open","indexOf","close","slice","parseAddressList","addresses","Array","isArray","map","a","String","filter","length","split","LEGACY_HOST_REGEX","MAX_INPUT_LEN_FOR_LEGACY_HOST_REGEX","getHostFromAddress","email","normalized","undefined","match","exec","groupRecipientsByDomain","recipients","groups","raw","host","push","collectRecipients","mail","to","cc","bcc"],"mappings":"AAAA;;;AAGC,0GAGM,SAASA,aAAaC,OAAe,EAAA;IAC1C,MAAMC,OAAAA,GAAUD,QAAQE,IAAI,EAAA;IAC5B,MAAMC,IAAAA,GAAOF,OAAAA,CAAQG,OAAO,CAAC,GAAA,CAAA;IAC7B,IAAID,IAAAA,KAAS,EAAC,EAAG;QACf,OAAOF,OAAAA;AACT,IAAA;AACA,IAAA,MAAMI,KAAAA,GAAQJ,OAAAA,CAAQG,OAAO,CAAC,KAAKD,IAAAA,GAAO,CAAA,CAAA;IAC1C,IAAIE,KAAAA,KAAU,EAAC,EAAG;QAChB,OAAOJ,OAAAA;AACT,IAAA;AACA,IAAA,OAAOA,QAAQK,KAAK,CAACH,IAAAA,GAAO,CAAA,EAAGE,OAAOH,IAAI,EAAA;AAC5C;AAEA,4EACO,SAASK,gBAAAA,CAAiBC,SAAwC,EAAA;AACvE,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA,OAAO,EAAE;AACX,IAAA;IACA,IAAIC,KAAAA,CAAMC,OAAO,CAACF,SAAAA,CAAAA,EAAY;AAC5B,QAAA,OAAOA,SAAAA,CAAUG,GAAG,CAAC,CAACC,IAAMb,YAAAA,CAAac,MAAAA,CAAOD,CAAAA,CAAAA,CAAAA,CAAAA,CAAKE,MAAM,CAAC,CAACF,CAAAA,GAAMA,CAAAA,CAAEG,MAAM,GAAG,CAAA,CAAA;AAChF,IAAA;AACA,IAAA,OAAOP,UACJQ,KAAK,CAAC,GAAA,CAAA,CACNL,GAAG,CAAC,CAACC,CAAAA,GAAMb,YAAAA,CAAaa,CAAAA,CAAAA,CAAAA,CACxBE,MAAM,CAAC,CAACF,CAAAA,GAAMA,CAAAA,CAAEG,MAAM,GAAG,CAAA,CAAA;AAC9B;AAEA,+EACA,MAAME,iBAAAA,GAAoB,oBAAA;AAE1B;;;AAGC,IACD,MAAMC,mCAAAA,GAAsC,GAAA;AAE5C,6DACO,SAASC,kBAAAA,CAAmBC,KAAa,EAAA;AAC9C,IAAA,MAAMC,aAAatB,YAAAA,CAAaqB,KAAAA,CAAAA;IAChC,IAAIC,UAAAA,CAAWN,MAAM,GAAGG,mCAAAA,EAAqC;QAC3D,OAAOI,SAAAA;AACT,IAAA;IACA,MAAMC,KAAAA,GAAQN,iBAAAA,CAAkBO,IAAI,CAACH,UAAAA,CAAAA;IACrC,OAAOE,KAAAA,GAAQ,CAAA,CAAE;AACnB;AAEA,uEACO,SAASE,uBAAAA,CAAwBC,UAAoB,EAAA;AAC1D,IAAA,MAAMC,SAAmC,EAAC;IAC1C,KAAK,MAAMC,OAAOF,UAAAA,CAAY;QAC5B,MAAMG,IAAAA,GAAOhB,OAAOM,kBAAAA,CAAmBS,GAAAA,CAAAA,CAAAA;AACvC,QAAA,IAAI,CAACD,MAAM,CAACE,IAAAA,CAAK,EAAE;YACjBF,MAAM,CAACE,IAAAA,CAAK,GAAG,EAAE;AACnB,QAAA;AACAF,QAAAA,MAAM,CAACE,IAAAA,CAAK,CAACC,IAAI,CAACF,GAAAA,CAAAA;AACpB,IAAA;IACA,OAAOD,MAAAA;AACT;AAEA,oFACO,SAASI,iBAAAA,CAAkBC,IAIjC,EAAA;IACC,OAAO;AACFzB,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKC,EAAE,CAAA;AACxB1B,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKE,EAAE,CAAA;AACxB3B,QAAAA,GAAAA,gBAAAA,CAAiByB,KAAKG,GAAG;AAC7B,KAAA;AACH;;;;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SendMailOptions } from 'nodemailer';
|
|
2
|
+
import type { ProviderSendmailOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Build list of SMTP peers to try for a recipient domain (MX resolution or dev server),
|
|
5
|
+
* matching guileen/node-sendmail `connectMx` + `smtpHost` append behavior.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveMxHosts(domain: string, options: ProviderSendmailOptions): Promise<Array<{
|
|
8
|
+
exchange: string;
|
|
9
|
+
}>>;
|
|
10
|
+
/**
|
|
11
|
+
* Direct SMTP delivery (per recipient domain, per MX with fallback), replacing the
|
|
12
|
+
* unmaintained `sendmail` npm package while preserving the same routing semantics.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sendDirectSmtp(mail: SendMailOptions, providerOptions: ProviderSendmailOptions): Promise<void>;
|
|
15
|
+
//# sourceMappingURL=direct-smtp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direct-smtp.d.ts","sourceRoot":"","sources":["../src/direct-smtp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,YAAY,CAAC;AASnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAiCvD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CA+BtC;AA0CD;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,eAAe,EACrB,eAAe,EAAE,uBAAuB,GACvC,OAAO,CAAC,IAAI,CAAC,CA6Ef"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promises = require('dns/promises');
|
|
4
|
+
var os = require('os');
|
|
5
|
+
var nodemailer = require('nodemailer');
|
|
6
|
+
var addressing = require('./addressing.js');
|
|
7
|
+
var logger = require('./logger.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mirrors legacy `const devPort = options.devPort || -1` (guileen/node-sendmail).
|
|
11
|
+
* `0` is falsy → `-1` (MX mode). `true` is truthy and was passed to `createConnection` (Node
|
|
12
|
+
* coerces port `true` to `1`).
|
|
13
|
+
*/ function getEffectiveDevPort(options) {
|
|
14
|
+
const v = options.devPort;
|
|
15
|
+
if (v === undefined || v === false || v === null) {
|
|
16
|
+
return -1;
|
|
17
|
+
}
|
|
18
|
+
if (v === true) {
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
if (typeof v === 'number') {
|
|
22
|
+
return v || -1;
|
|
23
|
+
}
|
|
24
|
+
return -1;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Legacy `sendmail` connects with `createConnection(devPort, devHost)` in dev mode — the
|
|
28
|
+
* dev port is the SMTP port. Otherwise use `smtpPort` (default 25).
|
|
29
|
+
*/ function getOutboundSmtpPort(options) {
|
|
30
|
+
const dev = getEffectiveDevPort(options);
|
|
31
|
+
if (dev !== -1) {
|
|
32
|
+
return dev;
|
|
33
|
+
}
|
|
34
|
+
return options.smtpPort || 25;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build list of SMTP peers to try for a recipient domain (MX resolution or dev server),
|
|
38
|
+
* matching guileen/node-sendmail `connectMx` + `smtpHost` append behavior.
|
|
39
|
+
*/ async function resolveMxHosts(domain, options) {
|
|
40
|
+
const devPort = getEffectiveDevPort(options);
|
|
41
|
+
const devHost = options.devHost || 'localhost';
|
|
42
|
+
if (devPort !== -1) {
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
exchange: devHost
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
let records;
|
|
50
|
+
try {
|
|
51
|
+
records = await promises.resolveMx(domain);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
54
|
+
throw new Error(`can not resolve Mx of <${domain}>: ${e.message}`);
|
|
55
|
+
}
|
|
56
|
+
if (!records || records.length === 0) {
|
|
57
|
+
throw new Error(`can not resolve Mx of <${domain}>`);
|
|
58
|
+
}
|
|
59
|
+
records.sort((a, b)=>a.priority - b.priority);
|
|
60
|
+
// Mirror legacy `const smtpHost = options.smtpHost || -1` behavior: falsy (eg 0) disables.
|
|
61
|
+
const smtpHost = options.smtpHost || -1;
|
|
62
|
+
if (smtpHost !== -1) {
|
|
63
|
+
records.push({
|
|
64
|
+
exchange: String(smtpHost),
|
|
65
|
+
priority: 9999
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return records.map((r)=>({
|
|
69
|
+
exchange: r.exchange.replace(/\.$/, '')
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
async function trySendViaHost(exchange, smtpPort, srcHost, fromEnvelope, recipients, mail, dkim, options) {
|
|
73
|
+
const transporter = nodemailer.createTransport({
|
|
74
|
+
host: exchange,
|
|
75
|
+
port: smtpPort,
|
|
76
|
+
secure: false,
|
|
77
|
+
// Legacy sendmail@1.6.1 uses plain SMTP sockets and does not attempt STARTTLS.
|
|
78
|
+
ignoreTLS: true,
|
|
79
|
+
requireTLS: false,
|
|
80
|
+
name: srcHost,
|
|
81
|
+
tls: {
|
|
82
|
+
rejectUnauthorized: options.rejectUnauthorized
|
|
83
|
+
},
|
|
84
|
+
connectionTimeout: 60000,
|
|
85
|
+
greetingTimeout: 30000,
|
|
86
|
+
socketTimeout: 60000
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
return await transporter.sendMail({
|
|
90
|
+
...mail,
|
|
91
|
+
envelope: {
|
|
92
|
+
from: fromEnvelope,
|
|
93
|
+
to: recipients
|
|
94
|
+
},
|
|
95
|
+
dkim
|
|
96
|
+
});
|
|
97
|
+
} finally{
|
|
98
|
+
transporter.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Direct SMTP delivery (per recipient domain, per MX with fallback), replacing the
|
|
103
|
+
* unmaintained `sendmail` npm package while preserving the same routing semantics.
|
|
104
|
+
*/ async function sendDirectSmtp(mail, providerOptions) {
|
|
105
|
+
const logger$1 = logger.createLogger(providerOptions);
|
|
106
|
+
const smtpPort = getOutboundSmtpPort(providerOptions);
|
|
107
|
+
const fromHeader = String(mail.from || '');
|
|
108
|
+
const fromAddr = addressing.extractEmail(fromHeader);
|
|
109
|
+
const fromHost = addressing.getHostFromAddress(fromAddr);
|
|
110
|
+
const srcHost = fromHost || os.hostname() || 'localhost';
|
|
111
|
+
const dkimOpt = providerOptions.dkim;
|
|
112
|
+
const dkim = dkimOpt && typeof dkimOpt === 'object' && 'privateKey' in dkimOpt ? {
|
|
113
|
+
domainName: srcHost,
|
|
114
|
+
keySelector: dkimOpt.keySelector || 'dkim',
|
|
115
|
+
privateKey: dkimOpt.privateKey
|
|
116
|
+
} : undefined;
|
|
117
|
+
const recipients = addressing.collectRecipients({
|
|
118
|
+
to: mail.to,
|
|
119
|
+
cc: mail.cc,
|
|
120
|
+
bcc: mail.bcc
|
|
121
|
+
});
|
|
122
|
+
if (recipients.length === 0) {
|
|
123
|
+
throw new Error('No recipients defined');
|
|
124
|
+
}
|
|
125
|
+
const groups = addressing.groupRecipientsByDomain(recipients);
|
|
126
|
+
const fromEnvelope = fromAddr;
|
|
127
|
+
let anyDomainDelivered = false;
|
|
128
|
+
for (const domain of Object.keys(groups)){
|
|
129
|
+
const domainRecipients = groups[domain];
|
|
130
|
+
let hosts;
|
|
131
|
+
try {
|
|
132
|
+
hosts = await resolveMxHosts(domain, providerOptions);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
logger$1.error('Sendmail provider: MX resolution failed', err);
|
|
135
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
136
|
+
}
|
|
137
|
+
let lastError;
|
|
138
|
+
let sent = false;
|
|
139
|
+
for (const { exchange } of hosts){
|
|
140
|
+
try {
|
|
141
|
+
await trySendViaHost(exchange, smtpPort, srcHost, fromEnvelope, domainRecipients, mail, dkim, providerOptions);
|
|
142
|
+
sent = true;
|
|
143
|
+
break;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
146
|
+
logger$1.error(`Sendmail provider: failed to send via ${exchange}:${smtpPort}`, lastError);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (sent) {
|
|
150
|
+
anyDomainDelivered = true;
|
|
151
|
+
} else {
|
|
152
|
+
logger$1.error(`Sendmail provider: failed to deliver for domain ${domain}`, lastError);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!anyDomainDelivered) {
|
|
156
|
+
throw new Error('Failed to deliver mail for all recipient domains');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
exports.resolveMxHosts = resolveMxHosts;
|
|
161
|
+
exports.sendDirectSmtp = sendDirectSmtp;
|
|
162
|
+
//# sourceMappingURL=direct-smtp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direct-smtp.js","sources":["../src/direct-smtp.ts"],"sourcesContent":["import { resolveMx as dnsResolveMx } from 'dns/promises';\nimport { hostname as osHostname } from 'os';\nimport nodemailer from 'nodemailer';\nimport type { SentMessageInfo, SendMailOptions } from 'nodemailer';\n\nimport {\n collectRecipients,\n extractEmail,\n getHostFromAddress,\n groupRecipientsByDomain,\n} from './addressing';\nimport { createLogger } from './logger';\nimport type { ProviderSendmailOptions } from './types';\n\n/**\n * Mirrors legacy `const devPort = options.devPort || -1` (guileen/node-sendmail).\n * `0` is falsy → `-1` (MX mode). `true` is truthy and was passed to `createConnection` (Node\n * coerces port `true` to `1`).\n */\nfunction getEffectiveDevPort(options: ProviderSendmailOptions): number {\n const v = options.devPort;\n if (v === undefined || v === false || v === null) {\n return -1;\n }\n if (v === true) {\n return 1;\n }\n if (typeof v === 'number') {\n return v || -1;\n }\n return -1;\n}\n\n/**\n * Legacy `sendmail` connects with `createConnection(devPort, devHost)` in dev mode — the\n * dev port is the SMTP port. Otherwise use `smtpPort` (default 25).\n */\nfunction getOutboundSmtpPort(options: ProviderSendmailOptions): number {\n const dev = getEffectiveDevPort(options);\n if (dev !== -1) {\n return dev;\n }\n return options.smtpPort || 25;\n}\n\n/**\n * Build list of SMTP peers to try for a recipient domain (MX resolution or dev server),\n * matching guileen/node-sendmail `connectMx` + `smtpHost` append behavior.\n */\nexport async function resolveMxHosts(\n domain: string,\n options: ProviderSendmailOptions\n): Promise<Array<{ exchange: string }>> {\n const devPort = getEffectiveDevPort(options);\n const devHost = options.devHost || 'localhost';\n\n if (devPort !== -1) {\n return [{ exchange: devHost }];\n }\n\n let records: Awaited<ReturnType<typeof dnsResolveMx>>;\n try {\n records = await dnsResolveMx(domain);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n throw new Error(`can not resolve Mx of <${domain}>: ${e.message}`);\n }\n\n if (!records || records.length === 0) {\n throw new Error(`can not resolve Mx of <${domain}>`);\n }\n\n records.sort((a, b) => a.priority - b.priority);\n\n // Mirror legacy `const smtpHost = options.smtpHost || -1` behavior: falsy (eg 0) disables.\n const smtpHost = options.smtpHost || -1;\n if (smtpHost !== -1) {\n records.push({ exchange: String(smtpHost), priority: 9999 });\n }\n\n return records.map((r) => ({\n exchange: r.exchange.replace(/\\.$/, ''),\n }));\n}\n\nasync function trySendViaHost(\n exchange: string,\n smtpPort: number,\n srcHost: string,\n fromEnvelope: string,\n recipients: string[],\n mail: SendMailOptions,\n dkim: SendMailOptions['dkim'],\n options: ProviderSendmailOptions\n): Promise<SentMessageInfo> {\n const transporter = nodemailer.createTransport({\n host: exchange,\n port: smtpPort,\n secure: false,\n // Legacy sendmail@1.6.1 uses plain SMTP sockets and does not attempt STARTTLS.\n ignoreTLS: true,\n requireTLS: false,\n name: srcHost,\n tls: {\n rejectUnauthorized: options.rejectUnauthorized,\n },\n connectionTimeout: 60_000,\n greetingTimeout: 30_000,\n socketTimeout: 60_000,\n });\n\n try {\n return await transporter.sendMail({\n ...mail,\n envelope: {\n from: fromEnvelope,\n to: recipients,\n },\n dkim,\n });\n } finally {\n transporter.close();\n }\n}\n\n/**\n * Direct SMTP delivery (per recipient domain, per MX with fallback), replacing the\n * unmaintained `sendmail` npm package while preserving the same routing semantics.\n */\nexport async function sendDirectSmtp(\n mail: SendMailOptions,\n providerOptions: ProviderSendmailOptions\n): Promise<void> {\n const logger = createLogger(providerOptions);\n const smtpPort = getOutboundSmtpPort(providerOptions);\n\n const fromHeader = String(mail.from || '');\n const fromAddr = extractEmail(fromHeader);\n const fromHost = getHostFromAddress(fromAddr);\n const srcHost = fromHost || osHostname() || 'localhost';\n\n const dkimOpt = providerOptions.dkim;\n const dkim: SendMailOptions['dkim'] =\n dkimOpt && typeof dkimOpt === 'object' && 'privateKey' in dkimOpt\n ? {\n domainName: srcHost,\n keySelector: dkimOpt.keySelector || 'dkim',\n privateKey: dkimOpt.privateKey,\n }\n : undefined;\n\n const recipients = collectRecipients({\n to: mail.to as string | string[] | undefined,\n cc: mail.cc as string | string[] | undefined,\n bcc: mail.bcc as string | string[] | undefined,\n });\n\n if (recipients.length === 0) {\n throw new Error('No recipients defined');\n }\n\n const groups = groupRecipientsByDomain(recipients);\n const fromEnvelope = fromAddr;\n\n let anyDomainDelivered = false;\n\n for (const domain of Object.keys(groups)) {\n const domainRecipients = groups[domain];\n let hosts: Array<{ exchange: string }>;\n try {\n hosts = await resolveMxHosts(domain, providerOptions);\n } catch (err) {\n logger.error('Sendmail provider: MX resolution failed', err);\n throw err instanceof Error ? err : new Error(String(err));\n }\n\n let lastError: Error | undefined;\n let sent = false;\n\n for (const { exchange } of hosts) {\n try {\n await trySendViaHost(\n exchange,\n smtpPort,\n srcHost,\n fromEnvelope,\n domainRecipients,\n mail,\n dkim,\n providerOptions\n );\n sent = true;\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n logger.error(`Sendmail provider: failed to send via ${exchange}:${smtpPort}`, lastError);\n }\n }\n\n if (sent) {\n anyDomainDelivered = true;\n } else {\n logger.error(`Sendmail provider: failed to deliver for domain ${domain}`, lastError);\n }\n }\n\n if (!anyDomainDelivered) {\n throw new Error('Failed to deliver mail for all recipient domains');\n }\n}\n"],"names":["getEffectiveDevPort","options","v","devPort","undefined","getOutboundSmtpPort","dev","smtpPort","resolveMxHosts","domain","devHost","exchange","records","dnsResolveMx","err","e","Error","String","message","length","sort","a","b","priority","smtpHost","push","map","r","replace","trySendViaHost","srcHost","fromEnvelope","recipients","mail","dkim","transporter","nodemailer","createTransport","host","port","secure","ignoreTLS","requireTLS","name","tls","rejectUnauthorized","connectionTimeout","greetingTimeout","socketTimeout","sendMail","envelope","from","to","close","sendDirectSmtp","providerOptions","logger","createLogger","fromHeader","fromAddr","extractEmail","fromHost","getHostFromAddress","osHostname","dkimOpt","domainName","keySelector","privateKey","collectRecipients","cc","bcc","groups","groupRecipientsByDomain","anyDomainDelivered","Object","keys","domainRecipients","hosts","error","lastError","sent"],"mappings":"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"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { resolveMx } from 'dns/promises';
|
|
2
|
+
import { hostname } from 'os';
|
|
3
|
+
import nodemailer from 'nodemailer';
|
|
4
|
+
import { extractEmail, getHostFromAddress, collectRecipients, groupRecipientsByDomain } from './addressing.mjs';
|
|
5
|
+
import { createLogger } from './logger.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors legacy `const devPort = options.devPort || -1` (guileen/node-sendmail).
|
|
9
|
+
* `0` is falsy → `-1` (MX mode). `true` is truthy and was passed to `createConnection` (Node
|
|
10
|
+
* coerces port `true` to `1`).
|
|
11
|
+
*/ function getEffectiveDevPort(options) {
|
|
12
|
+
const v = options.devPort;
|
|
13
|
+
if (v === undefined || v === false || v === null) {
|
|
14
|
+
return -1;
|
|
15
|
+
}
|
|
16
|
+
if (v === true) {
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
if (typeof v === 'number') {
|
|
20
|
+
return v || -1;
|
|
21
|
+
}
|
|
22
|
+
return -1;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Legacy `sendmail` connects with `createConnection(devPort, devHost)` in dev mode — the
|
|
26
|
+
* dev port is the SMTP port. Otherwise use `smtpPort` (default 25).
|
|
27
|
+
*/ function getOutboundSmtpPort(options) {
|
|
28
|
+
const dev = getEffectiveDevPort(options);
|
|
29
|
+
if (dev !== -1) {
|
|
30
|
+
return dev;
|
|
31
|
+
}
|
|
32
|
+
return options.smtpPort || 25;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build list of SMTP peers to try for a recipient domain (MX resolution or dev server),
|
|
36
|
+
* matching guileen/node-sendmail `connectMx` + `smtpHost` append behavior.
|
|
37
|
+
*/ async function resolveMxHosts(domain, options) {
|
|
38
|
+
const devPort = getEffectiveDevPort(options);
|
|
39
|
+
const devHost = options.devHost || 'localhost';
|
|
40
|
+
if (devPort !== -1) {
|
|
41
|
+
return [
|
|
42
|
+
{
|
|
43
|
+
exchange: devHost
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
let records;
|
|
48
|
+
try {
|
|
49
|
+
records = await resolveMx(domain);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
52
|
+
throw new Error(`can not resolve Mx of <${domain}>: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (!records || records.length === 0) {
|
|
55
|
+
throw new Error(`can not resolve Mx of <${domain}>`);
|
|
56
|
+
}
|
|
57
|
+
records.sort((a, b)=>a.priority - b.priority);
|
|
58
|
+
// Mirror legacy `const smtpHost = options.smtpHost || -1` behavior: falsy (eg 0) disables.
|
|
59
|
+
const smtpHost = options.smtpHost || -1;
|
|
60
|
+
if (smtpHost !== -1) {
|
|
61
|
+
records.push({
|
|
62
|
+
exchange: String(smtpHost),
|
|
63
|
+
priority: 9999
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return records.map((r)=>({
|
|
67
|
+
exchange: r.exchange.replace(/\.$/, '')
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
async function trySendViaHost(exchange, smtpPort, srcHost, fromEnvelope, recipients, mail, dkim, options) {
|
|
71
|
+
const transporter = nodemailer.createTransport({
|
|
72
|
+
host: exchange,
|
|
73
|
+
port: smtpPort,
|
|
74
|
+
secure: false,
|
|
75
|
+
// Legacy sendmail@1.6.1 uses plain SMTP sockets and does not attempt STARTTLS.
|
|
76
|
+
ignoreTLS: true,
|
|
77
|
+
requireTLS: false,
|
|
78
|
+
name: srcHost,
|
|
79
|
+
tls: {
|
|
80
|
+
rejectUnauthorized: options.rejectUnauthorized
|
|
81
|
+
},
|
|
82
|
+
connectionTimeout: 60000,
|
|
83
|
+
greetingTimeout: 30000,
|
|
84
|
+
socketTimeout: 60000
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
return await transporter.sendMail({
|
|
88
|
+
...mail,
|
|
89
|
+
envelope: {
|
|
90
|
+
from: fromEnvelope,
|
|
91
|
+
to: recipients
|
|
92
|
+
},
|
|
93
|
+
dkim
|
|
94
|
+
});
|
|
95
|
+
} finally{
|
|
96
|
+
transporter.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Direct SMTP delivery (per recipient domain, per MX with fallback), replacing the
|
|
101
|
+
* unmaintained `sendmail` npm package while preserving the same routing semantics.
|
|
102
|
+
*/ async function sendDirectSmtp(mail, providerOptions) {
|
|
103
|
+
const logger = createLogger(providerOptions);
|
|
104
|
+
const smtpPort = getOutboundSmtpPort(providerOptions);
|
|
105
|
+
const fromHeader = String(mail.from || '');
|
|
106
|
+
const fromAddr = extractEmail(fromHeader);
|
|
107
|
+
const fromHost = getHostFromAddress(fromAddr);
|
|
108
|
+
const srcHost = fromHost || hostname() || 'localhost';
|
|
109
|
+
const dkimOpt = providerOptions.dkim;
|
|
110
|
+
const dkim = dkimOpt && typeof dkimOpt === 'object' && 'privateKey' in dkimOpt ? {
|
|
111
|
+
domainName: srcHost,
|
|
112
|
+
keySelector: dkimOpt.keySelector || 'dkim',
|
|
113
|
+
privateKey: dkimOpt.privateKey
|
|
114
|
+
} : undefined;
|
|
115
|
+
const recipients = collectRecipients({
|
|
116
|
+
to: mail.to,
|
|
117
|
+
cc: mail.cc,
|
|
118
|
+
bcc: mail.bcc
|
|
119
|
+
});
|
|
120
|
+
if (recipients.length === 0) {
|
|
121
|
+
throw new Error('No recipients defined');
|
|
122
|
+
}
|
|
123
|
+
const groups = groupRecipientsByDomain(recipients);
|
|
124
|
+
const fromEnvelope = fromAddr;
|
|
125
|
+
let anyDomainDelivered = false;
|
|
126
|
+
for (const domain of Object.keys(groups)){
|
|
127
|
+
const domainRecipients = groups[domain];
|
|
128
|
+
let hosts;
|
|
129
|
+
try {
|
|
130
|
+
hosts = await resolveMxHosts(domain, providerOptions);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error('Sendmail provider: MX resolution failed', err);
|
|
133
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
134
|
+
}
|
|
135
|
+
let lastError;
|
|
136
|
+
let sent = false;
|
|
137
|
+
for (const { exchange } of hosts){
|
|
138
|
+
try {
|
|
139
|
+
await trySendViaHost(exchange, smtpPort, srcHost, fromEnvelope, domainRecipients, mail, dkim, providerOptions);
|
|
140
|
+
sent = true;
|
|
141
|
+
break;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
144
|
+
logger.error(`Sendmail provider: failed to send via ${exchange}:${smtpPort}`, lastError);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (sent) {
|
|
148
|
+
anyDomainDelivered = true;
|
|
149
|
+
} else {
|
|
150
|
+
logger.error(`Sendmail provider: failed to deliver for domain ${domain}`, lastError);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (!anyDomainDelivered) {
|
|
154
|
+
throw new Error('Failed to deliver mail for all recipient domains');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { resolveMxHosts, sendDirectSmtp };
|
|
159
|
+
//# sourceMappingURL=direct-smtp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"direct-smtp.mjs","sources":["../src/direct-smtp.ts"],"sourcesContent":["import { resolveMx as dnsResolveMx } from 'dns/promises';\nimport { hostname as osHostname } from 'os';\nimport nodemailer from 'nodemailer';\nimport type { SentMessageInfo, SendMailOptions } from 'nodemailer';\n\nimport {\n collectRecipients,\n extractEmail,\n getHostFromAddress,\n groupRecipientsByDomain,\n} from './addressing';\nimport { createLogger } from './logger';\nimport type { ProviderSendmailOptions } from './types';\n\n/**\n * Mirrors legacy `const devPort = options.devPort || -1` (guileen/node-sendmail).\n * `0` is falsy → `-1` (MX mode). `true` is truthy and was passed to `createConnection` (Node\n * coerces port `true` to `1`).\n */\nfunction getEffectiveDevPort(options: ProviderSendmailOptions): number {\n const v = options.devPort;\n if (v === undefined || v === false || v === null) {\n return -1;\n }\n if (v === true) {\n return 1;\n }\n if (typeof v === 'number') {\n return v || -1;\n }\n return -1;\n}\n\n/**\n * Legacy `sendmail` connects with `createConnection(devPort, devHost)` in dev mode — the\n * dev port is the SMTP port. Otherwise use `smtpPort` (default 25).\n */\nfunction getOutboundSmtpPort(options: ProviderSendmailOptions): number {\n const dev = getEffectiveDevPort(options);\n if (dev !== -1) {\n return dev;\n }\n return options.smtpPort || 25;\n}\n\n/**\n * Build list of SMTP peers to try for a recipient domain (MX resolution or dev server),\n * matching guileen/node-sendmail `connectMx` + `smtpHost` append behavior.\n */\nexport async function resolveMxHosts(\n domain: string,\n options: ProviderSendmailOptions\n): Promise<Array<{ exchange: string }>> {\n const devPort = getEffectiveDevPort(options);\n const devHost = options.devHost || 'localhost';\n\n if (devPort !== -1) {\n return [{ exchange: devHost }];\n }\n\n let records: Awaited<ReturnType<typeof dnsResolveMx>>;\n try {\n records = await dnsResolveMx(domain);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n throw new Error(`can not resolve Mx of <${domain}>: ${e.message}`);\n }\n\n if (!records || records.length === 0) {\n throw new Error(`can not resolve Mx of <${domain}>`);\n }\n\n records.sort((a, b) => a.priority - b.priority);\n\n // Mirror legacy `const smtpHost = options.smtpHost || -1` behavior: falsy (eg 0) disables.\n const smtpHost = options.smtpHost || -1;\n if (smtpHost !== -1) {\n records.push({ exchange: String(smtpHost), priority: 9999 });\n }\n\n return records.map((r) => ({\n exchange: r.exchange.replace(/\\.$/, ''),\n }));\n}\n\nasync function trySendViaHost(\n exchange: string,\n smtpPort: number,\n srcHost: string,\n fromEnvelope: string,\n recipients: string[],\n mail: SendMailOptions,\n dkim: SendMailOptions['dkim'],\n options: ProviderSendmailOptions\n): Promise<SentMessageInfo> {\n const transporter = nodemailer.createTransport({\n host: exchange,\n port: smtpPort,\n secure: false,\n // Legacy sendmail@1.6.1 uses plain SMTP sockets and does not attempt STARTTLS.\n ignoreTLS: true,\n requireTLS: false,\n name: srcHost,\n tls: {\n rejectUnauthorized: options.rejectUnauthorized,\n },\n connectionTimeout: 60_000,\n greetingTimeout: 30_000,\n socketTimeout: 60_000,\n });\n\n try {\n return await transporter.sendMail({\n ...mail,\n envelope: {\n from: fromEnvelope,\n to: recipients,\n },\n dkim,\n });\n } finally {\n transporter.close();\n }\n}\n\n/**\n * Direct SMTP delivery (per recipient domain, per MX with fallback), replacing the\n * unmaintained `sendmail` npm package while preserving the same routing semantics.\n */\nexport async function sendDirectSmtp(\n mail: SendMailOptions,\n providerOptions: ProviderSendmailOptions\n): Promise<void> {\n const logger = createLogger(providerOptions);\n const smtpPort = getOutboundSmtpPort(providerOptions);\n\n const fromHeader = String(mail.from || '');\n const fromAddr = extractEmail(fromHeader);\n const fromHost = getHostFromAddress(fromAddr);\n const srcHost = fromHost || osHostname() || 'localhost';\n\n const dkimOpt = providerOptions.dkim;\n const dkim: SendMailOptions['dkim'] =\n dkimOpt && typeof dkimOpt === 'object' && 'privateKey' in dkimOpt\n ? {\n domainName: srcHost,\n keySelector: dkimOpt.keySelector || 'dkim',\n privateKey: dkimOpt.privateKey,\n }\n : undefined;\n\n const recipients = collectRecipients({\n to: mail.to as string | string[] | undefined,\n cc: mail.cc as string | string[] | undefined,\n bcc: mail.bcc as string | string[] | undefined,\n });\n\n if (recipients.length === 0) {\n throw new Error('No recipients defined');\n }\n\n const groups = groupRecipientsByDomain(recipients);\n const fromEnvelope = fromAddr;\n\n let anyDomainDelivered = false;\n\n for (const domain of Object.keys(groups)) {\n const domainRecipients = groups[domain];\n let hosts: Array<{ exchange: string }>;\n try {\n hosts = await resolveMxHosts(domain, providerOptions);\n } catch (err) {\n logger.error('Sendmail provider: MX resolution failed', err);\n throw err instanceof Error ? err : new Error(String(err));\n }\n\n let lastError: Error | undefined;\n let sent = false;\n\n for (const { exchange } of hosts) {\n try {\n await trySendViaHost(\n exchange,\n smtpPort,\n srcHost,\n fromEnvelope,\n domainRecipients,\n mail,\n dkim,\n providerOptions\n );\n sent = true;\n break;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n logger.error(`Sendmail provider: failed to send via ${exchange}:${smtpPort}`, lastError);\n }\n }\n\n if (sent) {\n anyDomainDelivered = true;\n } else {\n logger.error(`Sendmail provider: failed to deliver for domain ${domain}`, lastError);\n }\n }\n\n if (!anyDomainDelivered) {\n throw new Error('Failed to deliver mail for all recipient domains');\n }\n}\n"],"names":["getEffectiveDevPort","options","v","devPort","undefined","getOutboundSmtpPort","dev","smtpPort","resolveMxHosts","domain","devHost","exchange","records","dnsResolveMx","err","e","Error","String","message","length","sort","a","b","priority","smtpHost","push","map","r","replace","trySendViaHost","srcHost","fromEnvelope","recipients","mail","dkim","transporter","nodemailer","createTransport","host","port","secure","ignoreTLS","requireTLS","name","tls","rejectUnauthorized","connectionTimeout","greetingTimeout","socketTimeout","sendMail","envelope","from","to","close","sendDirectSmtp","providerOptions","logger","createLogger","fromHeader","fromAddr","extractEmail","fromHost","getHostFromAddress","osHostname","dkimOpt","domainName","keySelector","privateKey","collectRecipients","cc","bcc","groups","groupRecipientsByDomain","anyDomainDelivered","Object","keys","domainRecipients","hosts","error","lastError","sent"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
interface Settings {
|
|
3
|
-
defaultFrom: string;
|
|
4
|
-
defaultReplyTo: string;
|
|
5
|
-
}
|
|
1
|
+
import type { ProviderSendmailOptions, Settings } from './types';
|
|
6
2
|
interface SendOptions {
|
|
7
3
|
from?: string;
|
|
8
4
|
to: string;
|
|
9
|
-
cc
|
|
10
|
-
bcc
|
|
5
|
+
cc?: string;
|
|
6
|
+
bcc?: string;
|
|
11
7
|
replyTo?: string;
|
|
12
8
|
subject: string;
|
|
13
|
-
text
|
|
14
|
-
html
|
|
9
|
+
text?: string;
|
|
10
|
+
html?: string;
|
|
15
11
|
[key: string]: unknown;
|
|
16
12
|
}
|
|
17
|
-
type ProviderOptions = Options;
|
|
18
13
|
declare const _default: {
|
|
19
|
-
init(providerOptions:
|
|
14
|
+
init(providerOptions: ProviderSendmailOptions, settings: Settings): {
|
|
20
15
|
send(options: SendOptions): Promise<void>;
|
|
21
16
|
};
|
|
22
17
|
};
|
|
23
18
|
export default _default;
|
|
19
|
+
export type { ProviderSendmailOptions, Settings } from './types';
|
|
24
20
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,WAAW;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;;0BAGuB,uBAAuB,YAAY,QAAQ;sBAO/C,WAAW,GAAG,QAAQ,IAAI,CAAC;;;AAR/C,wBA2BE;AAEF,YAAY,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var directSmtp = require('./direct-smtp.js');
|
|
4
4
|
|
|
5
5
|
var index = {
|
|
6
6
|
init (providerOptions, settings) {
|
|
7
|
-
const
|
|
7
|
+
const mergedOptions = {
|
|
8
8
|
silent: true,
|
|
9
9
|
...providerOptions
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
11
|
return {
|
|
12
12
|
send (options) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
sendmail(msg, (err)=>{
|
|
27
|
-
if (err) {
|
|
28
|
-
reject(err);
|
|
29
|
-
} else {
|
|
30
|
-
resolve();
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
13
|
+
const { from, to, cc, bcc, replyTo, subject, text, html, ...rest } = options;
|
|
14
|
+
const mail = {
|
|
15
|
+
from: from || settings.defaultFrom,
|
|
16
|
+
to,
|
|
17
|
+
cc,
|
|
18
|
+
bcc,
|
|
19
|
+
replyTo: replyTo || settings.defaultReplyTo,
|
|
20
|
+
subject,
|
|
21
|
+
text,
|
|
22
|
+
html,
|
|
23
|
+
...rest
|
|
24
|
+
};
|
|
25
|
+
return directSmtp.sendDirectSmtp(mail, mergedOptions);
|
|
34
26
|
}
|
|
35
27
|
};
|
|
36
28
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import type { SendMailOptions } from 'nodemailer';\n\nimport { sendDirectSmtp } from './direct-smtp';\nimport type { ProviderSendmailOptions, Settings } from './types';\n\ninterface SendOptions {\n from?: string;\n to: string;\n cc?: string;\n bcc?: string;\n replyTo?: string;\n subject: string;\n text?: string;\n html?: string;\n [key: string]: unknown;\n}\n\nexport default {\n init(providerOptions: ProviderSendmailOptions, settings: Settings) {\n const mergedOptions: ProviderSendmailOptions = {\n silent: true,\n ...providerOptions,\n };\n\n return {\n send(options: SendOptions): Promise<void> {\n const { from, to, cc, bcc, replyTo, subject, text, html, ...rest } = options;\n\n const mail: SendMailOptions = {\n from: from || settings.defaultFrom,\n to,\n cc,\n bcc,\n replyTo: replyTo || settings.defaultReplyTo,\n subject,\n text,\n html,\n ...(rest as Record<string, unknown>),\n };\n\n return sendDirectSmtp(mail, mergedOptions);\n },\n };\n },\n};\n\nexport type { ProviderSendmailOptions, Settings } from './types';\n"],"names":["init","providerOptions","settings","mergedOptions","silent","send","options","from","to","cc","bcc","replyTo","subject","text","html","rest","mail","defaultFrom","defaultReplyTo","sendDirectSmtp"],"mappings":";;;;AAiBA,YAAe;IACbA,IAAAA,CAAAA,CAAKC,eAAwC,EAAEC,QAAkB,EAAA;AAC/D,QAAA,MAAMC,aAAAA,GAAyC;YAC7CC,MAAAA,EAAQ,IAAA;AACR,YAAA,GAAGH;AACL,SAAA;QAEA,OAAO;AACLI,YAAAA,IAAAA,CAAAA,CAAKC,OAAoB,EAAA;AACvB,gBAAA,MAAM,EAAEC,IAAI,EAAEC,EAAE,EAAEC,EAAE,EAAEC,GAAG,EAAEC,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGC,MAAM,GAAGT,OAAAA;AAErE,gBAAA,MAAMU,IAAAA,GAAwB;oBAC5BT,IAAAA,EAAMA,IAAAA,IAAQL,SAASe,WAAW;AAClCT,oBAAAA,EAAAA;AACAC,oBAAAA,EAAAA;AACAC,oBAAAA,GAAAA;oBACAC,OAAAA,EAASA,OAAAA,IAAWT,SAASgB,cAAc;AAC3CN,oBAAAA,OAAAA;AACAC,oBAAAA,IAAAA;AACAC,oBAAAA,IAAAA;AACA,oBAAA,GAAIC;AACN,iBAAA;AAEA,gBAAA,OAAOI,0BAAeH,IAAAA,EAAMb,aAAAA,CAAAA;AAC9B,YAAA;AACF,SAAA;AACF,IAAA;AACF,CAAA;;;;"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { sendDirectSmtp } from './direct-smtp.mjs';
|
|
2
2
|
|
|
3
3
|
var index = {
|
|
4
4
|
init (providerOptions, settings) {
|
|
5
|
-
const
|
|
5
|
+
const mergedOptions = {
|
|
6
6
|
silent: true,
|
|
7
7
|
...providerOptions
|
|
8
|
-
}
|
|
8
|
+
};
|
|
9
9
|
return {
|
|
10
10
|
send (options) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
sendmail(msg, (err)=>{
|
|
25
|
-
if (err) {
|
|
26
|
-
reject(err);
|
|
27
|
-
} else {
|
|
28
|
-
resolve();
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
});
|
|
11
|
+
const { from, to, cc, bcc, replyTo, subject, text, html, ...rest } = options;
|
|
12
|
+
const mail = {
|
|
13
|
+
from: from || settings.defaultFrom,
|
|
14
|
+
to,
|
|
15
|
+
cc,
|
|
16
|
+
bcc,
|
|
17
|
+
replyTo: replyTo || settings.defaultReplyTo,
|
|
18
|
+
subject,
|
|
19
|
+
text,
|
|
20
|
+
html,
|
|
21
|
+
...rest
|
|
22
|
+
};
|
|
23
|
+
return sendDirectSmtp(mail, mergedOptions);
|
|
32
24
|
}
|
|
33
25
|
};
|
|
34
26
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/index.ts"],"sourcesContent":["import type { SendMailOptions } from 'nodemailer';\n\nimport { sendDirectSmtp } from './direct-smtp';\nimport type { ProviderSendmailOptions, Settings } from './types';\n\ninterface SendOptions {\n from?: string;\n to: string;\n cc?: string;\n bcc?: string;\n replyTo?: string;\n subject: string;\n text?: string;\n html?: string;\n [key: string]: unknown;\n}\n\nexport default {\n init(providerOptions: ProviderSendmailOptions, settings: Settings) {\n const mergedOptions: ProviderSendmailOptions = {\n silent: true,\n ...providerOptions,\n };\n\n return {\n send(options: SendOptions): Promise<void> {\n const { from, to, cc, bcc, replyTo, subject, text, html, ...rest } = options;\n\n const mail: SendMailOptions = {\n from: from || settings.defaultFrom,\n to,\n cc,\n bcc,\n replyTo: replyTo || settings.defaultReplyTo,\n subject,\n text,\n html,\n ...(rest as Record<string, unknown>),\n };\n\n return sendDirectSmtp(mail, mergedOptions);\n },\n };\n },\n};\n\nexport type { ProviderSendmailOptions, Settings } from './types';\n"],"names":["init","providerOptions","settings","mergedOptions","silent","send","options","from","to","cc","bcc","replyTo","subject","text","html","rest","mail","defaultFrom","defaultReplyTo","sendDirectSmtp"],"mappings":";;AAiBA,YAAe;IACbA,IAAAA,CAAAA,CAAKC,eAAwC,EAAEC,QAAkB,EAAA;AAC/D,QAAA,MAAMC,aAAAA,GAAyC;YAC7CC,MAAAA,EAAQ,IAAA;AACR,YAAA,GAAGH;AACL,SAAA;QAEA,OAAO;AACLI,YAAAA,IAAAA,CAAAA,CAAKC,OAAoB,EAAA;AACvB,gBAAA,MAAM,EAAEC,IAAI,EAAEC,EAAE,EAAEC,EAAE,EAAEC,GAAG,EAAEC,OAAO,EAAEC,OAAO,EAAEC,IAAI,EAAEC,IAAI,EAAE,GAAGC,MAAM,GAAGT,OAAAA;AAErE,gBAAA,MAAMU,IAAAA,GAAwB;oBAC5BT,IAAAA,EAAMA,IAAAA,IAAQL,SAASe,WAAW;AAClCT,oBAAAA,EAAAA;AACAC,oBAAAA,EAAAA;AACAC,oBAAAA,GAAAA;oBACAC,OAAAA,EAASA,OAAAA,IAAWT,SAASgB,cAAc;AAC3CN,oBAAAA,OAAAA;AACAC,oBAAAA,IAAAA;AACAC,oBAAAA,IAAAA;AACA,oBAAA,GAAIC;AACN,iBAAA;AAEA,gBAAA,OAAOI,eAAeH,IAAAA,EAAMb,aAAAA,CAAAA;AAC9B,YAAA;AACF,SAAA;AACF,IAAA;AACF,CAAA;;;;"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProviderSendmailOptions } from './types';
|
|
2
|
+
interface Logger {
|
|
3
|
+
debug: (...args: unknown[]) => void;
|
|
4
|
+
info: (...args: unknown[]) => void;
|
|
5
|
+
warn: (...args: unknown[]) => void;
|
|
6
|
+
error: (...args: unknown[]) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Matches guileen/node-sendmail: `options.logger` wins over `silent` — if a custom logger is
|
|
10
|
+
* set, it is used as-is. Only when there is no custom logger does `silent` suppress output.
|
|
11
|
+
* Strapi merges `{ silent: true, ...providerOptions }` so explicit `silent: false` still applies.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createLogger(options: ProviderSendmailOptions): Logger;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEvD,UAAU,MAAM;IACd,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAsBrE"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function noop() {}
|
|
4
|
+
/**
|
|
5
|
+
* Matches guileen/node-sendmail: `options.logger` wins over `silent` — if a custom logger is
|
|
6
|
+
* set, it is used as-is. Only when there is no custom logger does `silent` suppress output.
|
|
7
|
+
* Strapi merges `{ silent: true, ...providerOptions }` so explicit `silent: false` still applies.
|
|
8
|
+
*/ function createLogger(options) {
|
|
9
|
+
if (options.logger) {
|
|
10
|
+
const l = options.logger;
|
|
11
|
+
return {
|
|
12
|
+
debug: l.debug ? l.debug.bind(l) : noop,
|
|
13
|
+
info: l.info ? l.info.bind(l) : noop,
|
|
14
|
+
warn: l.warn ? l.warn.bind(l) : noop,
|
|
15
|
+
error: l.error ? l.error.bind(l) : noop
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const silent = options.silent === true;
|
|
19
|
+
if (silent) {
|
|
20
|
+
return {
|
|
21
|
+
debug: noop,
|
|
22
|
+
info: noop,
|
|
23
|
+
warn: noop,
|
|
24
|
+
error: noop
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
debug: console.log,
|
|
29
|
+
info: console.info,
|
|
30
|
+
warn: console.warn,
|
|
31
|
+
error: console.error
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
exports.createLogger = createLogger;
|
|
36
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sources":["../src/logger.ts"],"sourcesContent":["import type { ProviderSendmailOptions } from './types';\n\ninterface Logger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\nfunction noop() {}\n\n/**\n * Matches guileen/node-sendmail: `options.logger` wins over `silent` — if a custom logger is\n * set, it is used as-is. Only when there is no custom logger does `silent` suppress output.\n * Strapi merges `{ silent: true, ...providerOptions }` so explicit `silent: false` still applies.\n */\nexport function createLogger(options: ProviderSendmailOptions): Logger {\n if (options.logger) {\n const l = options.logger;\n return {\n debug: l.debug ? l.debug.bind(l) : noop,\n info: l.info ? l.info.bind(l) : noop,\n warn: l.warn ? l.warn.bind(l) : noop,\n error: l.error ? l.error.bind(l) : noop,\n };\n }\n\n const silent = options.silent === true;\n if (silent) {\n return { debug: noop, info: noop, warn: noop, error: noop };\n }\n\n return {\n debug: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n}\n"],"names":["noop","createLogger","options","logger","l","debug","bind","info","warn","error","silent","console","log"],"mappings":";;AASA,SAASA,IAAAA,GAAAA,CAAQ;AAEjB;;;;IAKO,SAASC,YAAAA,CAAaC,OAAgC,EAAA;IAC3D,IAAIA,OAAAA,CAAQC,MAAM,EAAE;QAClB,MAAMC,CAAAA,GAAIF,QAAQC,MAAM;QACxB,OAAO;YACLE,KAAAA,EAAOD,CAAAA,CAAEC,KAAK,GAAGD,CAAAA,CAAEC,KAAK,CAACC,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YACnCO,IAAAA,EAAMH,CAAAA,CAAEG,IAAI,GAAGH,CAAAA,CAAEG,IAAI,CAACD,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YAChCQ,IAAAA,EAAMJ,CAAAA,CAAEI,IAAI,GAAGJ,CAAAA,CAAEI,IAAI,CAACF,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YAChCS,KAAAA,EAAOL,CAAAA,CAAEK,KAAK,GAAGL,CAAAA,CAAEK,KAAK,CAACH,IAAI,CAACF,CAAAA,CAAAA,GAAKJ;AACrC,SAAA;AACF,IAAA;IAEA,MAAMU,MAAAA,GAASR,OAAAA,CAAQQ,MAAM,KAAK,IAAA;AAClC,IAAA,IAAIA,MAAAA,EAAQ;QACV,OAAO;YAAEL,KAAAA,EAAOL,IAAAA;YAAMO,IAAAA,EAAMP,IAAAA;YAAMQ,IAAAA,EAAMR,IAAAA;YAAMS,KAAAA,EAAOT;AAAK,SAAA;AAC5D,IAAA;IAEA,OAAO;AACLK,QAAAA,KAAAA,EAAOM,QAAQC,GAAG;AAClBL,QAAAA,IAAAA,EAAMI,QAAQJ,IAAI;AAClBC,QAAAA,IAAAA,EAAMG,QAAQH,IAAI;AAClBC,QAAAA,KAAAA,EAAOE,QAAQF;AACjB,KAAA;AACF;;;;"}
|
package/dist/logger.mjs
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function noop() {}
|
|
2
|
+
/**
|
|
3
|
+
* Matches guileen/node-sendmail: `options.logger` wins over `silent` — if a custom logger is
|
|
4
|
+
* set, it is used as-is. Only when there is no custom logger does `silent` suppress output.
|
|
5
|
+
* Strapi merges `{ silent: true, ...providerOptions }` so explicit `silent: false` still applies.
|
|
6
|
+
*/ function createLogger(options) {
|
|
7
|
+
if (options.logger) {
|
|
8
|
+
const l = options.logger;
|
|
9
|
+
return {
|
|
10
|
+
debug: l.debug ? l.debug.bind(l) : noop,
|
|
11
|
+
info: l.info ? l.info.bind(l) : noop,
|
|
12
|
+
warn: l.warn ? l.warn.bind(l) : noop,
|
|
13
|
+
error: l.error ? l.error.bind(l) : noop
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const silent = options.silent === true;
|
|
17
|
+
if (silent) {
|
|
18
|
+
return {
|
|
19
|
+
debug: noop,
|
|
20
|
+
info: noop,
|
|
21
|
+
warn: noop,
|
|
22
|
+
error: noop
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
debug: console.log,
|
|
27
|
+
info: console.info,
|
|
28
|
+
warn: console.warn,
|
|
29
|
+
error: console.error
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { createLogger };
|
|
34
|
+
//# sourceMappingURL=logger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.mjs","sources":["../src/logger.ts"],"sourcesContent":["import type { ProviderSendmailOptions } from './types';\n\ninterface Logger {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n}\n\nfunction noop() {}\n\n/**\n * Matches guileen/node-sendmail: `options.logger` wins over `silent` — if a custom logger is\n * set, it is used as-is. Only when there is no custom logger does `silent` suppress output.\n * Strapi merges `{ silent: true, ...providerOptions }` so explicit `silent: false` still applies.\n */\nexport function createLogger(options: ProviderSendmailOptions): Logger {\n if (options.logger) {\n const l = options.logger;\n return {\n debug: l.debug ? l.debug.bind(l) : noop,\n info: l.info ? l.info.bind(l) : noop,\n warn: l.warn ? l.warn.bind(l) : noop,\n error: l.error ? l.error.bind(l) : noop,\n };\n }\n\n const silent = options.silent === true;\n if (silent) {\n return { debug: noop, info: noop, warn: noop, error: noop };\n }\n\n return {\n debug: console.log,\n info: console.info,\n warn: console.warn,\n error: console.error,\n };\n}\n"],"names":["noop","createLogger","options","logger","l","debug","bind","info","warn","error","silent","console","log"],"mappings":"AASA,SAASA,IAAAA,GAAAA,CAAQ;AAEjB;;;;IAKO,SAASC,YAAAA,CAAaC,OAAgC,EAAA;IAC3D,IAAIA,OAAAA,CAAQC,MAAM,EAAE;QAClB,MAAMC,CAAAA,GAAIF,QAAQC,MAAM;QACxB,OAAO;YACLE,KAAAA,EAAOD,CAAAA,CAAEC,KAAK,GAAGD,CAAAA,CAAEC,KAAK,CAACC,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YACnCO,IAAAA,EAAMH,CAAAA,CAAEG,IAAI,GAAGH,CAAAA,CAAEG,IAAI,CAACD,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YAChCQ,IAAAA,EAAMJ,CAAAA,CAAEI,IAAI,GAAGJ,CAAAA,CAAEI,IAAI,CAACF,IAAI,CAACF,CAAAA,CAAAA,GAAKJ,IAAAA;YAChCS,KAAAA,EAAOL,CAAAA,CAAEK,KAAK,GAAGL,CAAAA,CAAEK,KAAK,CAACH,IAAI,CAACF,CAAAA,CAAAA,GAAKJ;AACrC,SAAA;AACF,IAAA;IAEA,MAAMU,MAAAA,GAASR,OAAAA,CAAQQ,MAAM,KAAK,IAAA;AAClC,IAAA,IAAIA,MAAAA,EAAQ;QACV,OAAO;YAAEL,KAAAA,EAAOL,IAAAA;YAAMO,IAAAA,EAAMP,IAAAA;YAAMQ,IAAAA,EAAMR,IAAAA;YAAMS,KAAAA,EAAOT;AAAK,SAAA;AAC5D,IAAA;IAEA,OAAO;AACLK,QAAAA,KAAAA,EAAOM,QAAQC,GAAG;AAClBL,QAAAA,IAAAA,EAAMI,QAAQJ,IAAI;AAClBC,QAAAA,IAAAA,EAAMG,QAAQH,IAAI;AAClBC,QAAAA,KAAAA,EAAOE,QAAQF;AACjB,KAAA;AACF;;;;"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options passed through from Strapi `plugin::email` config `providerOptions`,
|
|
3
|
+
* historically compatible with `require('sendmail')` (guileen/node-sendmail).
|
|
4
|
+
*/
|
|
5
|
+
export interface ProviderSendmailOptions {
|
|
6
|
+
logger?: {
|
|
7
|
+
debug?: (...args: unknown[]) => void;
|
|
8
|
+
info?: (...args: unknown[]) => void;
|
|
9
|
+
warn?: (...args: unknown[]) => void;
|
|
10
|
+
error?: (...args: unknown[]) => void;
|
|
11
|
+
};
|
|
12
|
+
silent?: boolean;
|
|
13
|
+
dkim?: boolean | {
|
|
14
|
+
privateKey: string;
|
|
15
|
+
keySelector?: string;
|
|
16
|
+
};
|
|
17
|
+
/** Development / testing: connect to `devHost:devPort` instead of resolving MX. */
|
|
18
|
+
devPort?: number | boolean;
|
|
19
|
+
devHost?: string;
|
|
20
|
+
/** SMTP port for outbound delivery (default 25). */
|
|
21
|
+
smtpPort?: number;
|
|
22
|
+
/** Extra SMTP host to try after MX records (same as legacy `sendmail` package). */
|
|
23
|
+
smtpHost?: string | number;
|
|
24
|
+
rejectUnauthorized?: boolean;
|
|
25
|
+
autoEHLO?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export interface Settings {
|
|
28
|
+
defaultFrom: string;
|
|
29
|
+
defaultReplyTo?: string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QACpC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;QACpC,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;KACtC,CAAC;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EACD,OAAO,GACP;QACE,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACN,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/provider-email-sendmail",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.46.0",
|
|
4
4
|
"description": "Sendmail provider for strapi email",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"build:types": "run -T tsc -p tsconfig.build.json --emitDeclarationOnly",
|
|
42
42
|
"clean": "run -T rimraf ./dist",
|
|
43
43
|
"lint": "run -T eslint .",
|
|
44
|
+
"test:unit": "run -T jest --watchman=false",
|
|
44
45
|
"watch": "run -T rollup -c -w"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|
|
47
|
-
"
|
|
48
|
-
"sendmail": "^1.6.1"
|
|
48
|
+
"nodemailer": "8.0.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@types/
|
|
52
|
-
"eslint-config-custom": "5.
|
|
53
|
-
"tsconfig": "5.
|
|
51
|
+
"@types/nodemailer": "7.0.11",
|
|
52
|
+
"eslint-config-custom": "5.46.0",
|
|
53
|
+
"tsconfig": "5.46.0"
|
|
54
54
|
},
|
|
55
55
|
"engines": {
|
|
56
56
|
"node": ">=20.0.0 <=24.x.x",
|