@upyo/smtp 0.6.0-dev.295 → 0.6.0-dev.296
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 +17 -0
- package/dist/index.cjs +49 -4
- package/dist/index.js +49 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Features
|
|
|
28
28
|
- Multiple recipients (To, CC, BCC)
|
|
29
29
|
- SMTP PIPELINING for faster multi-recipient delivery
|
|
30
30
|
- SMTP SIZE declaration and advertised-limit checks
|
|
31
|
+
- SMTPUTF8 internationalized address delivery
|
|
31
32
|
- SMTP delivery status notification requests
|
|
32
33
|
- Custom headers
|
|
33
34
|
- Priority levels
|
|
@@ -183,6 +184,22 @@ See [RFC 1870] for the SMTP Message Size Declaration extension.
|
|
|
183
184
|
[RFC 1870]: https://www.rfc-editor.org/rfc/rfc1870
|
|
184
185
|
|
|
185
186
|
|
|
187
|
+
Internationalized addresses
|
|
188
|
+
---------------------------
|
|
189
|
+
|
|
190
|
+
The transport automatically negotiates [RFC 6531] SMTPUTF8 when any sender,
|
|
191
|
+
recipient, or reply-to mailbox contains a non-ASCII character. A supporting
|
|
192
|
+
server must advertise both `SMTPUTF8` and `8BITMIME`; Upyo then sends
|
|
193
|
+
`BODY=8BITMIME SMTPUTF8` on `MAIL FROM`.
|
|
194
|
+
|
|
195
|
+
If either capability is missing, delivery returns a non-retryable failed
|
|
196
|
+
receipt with the code `smtp.smtputf8-unsupported` before the mail transaction
|
|
197
|
+
starts. Unicode display names and subjects continue to use RFC 2047 encoding
|
|
198
|
+
and do not require SMTPUTF8 when the mailbox addresses remain ASCII-only.
|
|
199
|
+
|
|
200
|
+
[RFC 6531]: https://www.rfc-editor.org/rfc/rfc6531
|
|
201
|
+
|
|
202
|
+
|
|
186
203
|
Delivery status notifications
|
|
187
204
|
-----------------------------
|
|
188
205
|
|
package/dist/index.cjs
CHANGED
|
@@ -501,6 +501,29 @@ var SmtpMessageSizeError = class extends RangeError {
|
|
|
501
501
|
this.maximumSize = maximumSize;
|
|
502
502
|
}
|
|
503
503
|
};
|
|
504
|
+
/**
|
|
505
|
+
* Error thrown when an internationalized message requires an SMTP extension
|
|
506
|
+
* that the server did not advertise.
|
|
507
|
+
*
|
|
508
|
+
* The check happens before `MAIL FROM`, so the SMTP connection remains usable
|
|
509
|
+
* for another message.
|
|
510
|
+
*
|
|
511
|
+
* @since 0.6.0
|
|
512
|
+
*/
|
|
513
|
+
var SmtpUtf8UnsupportedError = class extends Error {
|
|
514
|
+
/** The required SMTP extension that the server did not advertise. */
|
|
515
|
+
missingCapability;
|
|
516
|
+
/**
|
|
517
|
+
* Creates an SMTPUTF8 support error.
|
|
518
|
+
*
|
|
519
|
+
* @param missingCapability The required extension that was not advertised.
|
|
520
|
+
*/
|
|
521
|
+
constructor(missingCapability) {
|
|
522
|
+
super(missingCapability === "SMTPUTF8" ? "The SMTP server does not advertise SMTPUTF8." : "The SMTP server advertises SMTPUTF8 without the required 8BITMIME capability.");
|
|
523
|
+
this.name = "SmtpUtf8UnsupportedError";
|
|
524
|
+
this.missingCapability = missingCapability;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
504
527
|
var SmtpPipelineTerminatedError = class extends Error {
|
|
505
528
|
responseIndex;
|
|
506
529
|
response;
|
|
@@ -974,6 +997,12 @@ var SmtpConnection = class {
|
|
|
974
997
|
}
|
|
975
998
|
async sendMessage(message, signal) {
|
|
976
999
|
signal?.throwIfAborted();
|
|
1000
|
+
let smtpUtf8Parameters = "";
|
|
1001
|
+
if (message.requiresSmtpUtf8 === true) {
|
|
1002
|
+
if (!this.capabilities.some((capability) => /^SMTPUTF8(?:[ \t]|$)/i.test(capability))) throw new SmtpUtf8UnsupportedError("SMTPUTF8");
|
|
1003
|
+
if (!this.capabilities.some((capability) => /^8BITMIME(?:[ \t]|$)/i.test(capability))) throw new SmtpUtf8UnsupportedError("8BITMIME");
|
|
1004
|
+
smtpUtf8Parameters = " BODY=8BITMIME SMTPUTF8";
|
|
1005
|
+
}
|
|
977
1006
|
const sizeCapability = parseSizeCapability(this.capabilities);
|
|
978
1007
|
let sizeParameter = "";
|
|
979
1008
|
if (sizeCapability != null) {
|
|
@@ -984,7 +1013,7 @@ var SmtpConnection = class {
|
|
|
984
1013
|
const dsn = message.envelope.dsn;
|
|
985
1014
|
if (dsn != null && !this.capabilities.some((capability) => /^DSN[ \t]*$/i.test(capability))) throw new SmtpDsnUnsupportedError();
|
|
986
1015
|
const mailDsnParameters = dsn == null || dsn.mailParameters.length === 0 ? "" : ` ${dsn.mailParameters.join(" ")}`;
|
|
987
|
-
const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${mailDsnParameters}`;
|
|
1016
|
+
const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
|
|
988
1017
|
const recipientCommands = message.envelope.to.map((recipient, index) => {
|
|
989
1018
|
const parameters = dsn?.recipientParameters[index] ?? [];
|
|
990
1019
|
const suffix = parameters.length === 0 ? "" : ` ${parameters.join(" ")}`;
|
|
@@ -1431,6 +1460,13 @@ async function convertMessage(message, dkimConfig, dsn) {
|
|
|
1431
1460
|
],
|
|
1432
1461
|
dsn
|
|
1433
1462
|
};
|
|
1463
|
+
const requiresSmtpUtf8 = [
|
|
1464
|
+
message.sender,
|
|
1465
|
+
...message.recipients,
|
|
1466
|
+
...message.ccRecipients,
|
|
1467
|
+
...message.bccRecipients,
|
|
1468
|
+
...message.replyRecipients
|
|
1469
|
+
].some((address) => Array.from(address.address).some((character) => (character.codePointAt(0) ?? 0) > 127));
|
|
1434
1470
|
let raw = await buildRawMessage(message);
|
|
1435
1471
|
if (dkimConfig) try {
|
|
1436
1472
|
for (const sig of dkimConfig.signatures) {
|
|
@@ -1443,7 +1479,8 @@ async function convertMessage(message, dkimConfig, dsn) {
|
|
|
1443
1479
|
}
|
|
1444
1480
|
return {
|
|
1445
1481
|
envelope,
|
|
1446
|
-
raw
|
|
1482
|
+
raw,
|
|
1483
|
+
requiresSmtpUtf8
|
|
1447
1484
|
};
|
|
1448
1485
|
}
|
|
1449
1486
|
async function buildRawMessage(message) {
|
|
@@ -1614,7 +1651,7 @@ function foldHeader(name, value) {
|
|
|
1614
1651
|
remaining = remaining.slice(whitespaceEnd);
|
|
1615
1652
|
}
|
|
1616
1653
|
lines.push(prefix + remaining);
|
|
1617
|
-
if (lines.some((line) => line
|
|
1654
|
+
if (lines.some((line) => node_buffer.Buffer.byteLength(line, "utf8") > 998)) throw new RangeError(`Header field ${name} contains a token too long to fold.`);
|
|
1618
1655
|
return lines.join("\r\n");
|
|
1619
1656
|
}
|
|
1620
1657
|
function encodeQuotedPrintable(text) {
|
|
@@ -1972,6 +2009,14 @@ function createSmtpFailure(message, error) {
|
|
|
1972
2009
|
maximumSize: error.maximumSize.toString()
|
|
1973
2010
|
}
|
|
1974
2011
|
});
|
|
2012
|
+
if (error instanceof SmtpUtf8UnsupportedError) return (0, __upyo_core.createFailedReceipt)(message, {
|
|
2013
|
+
provider: "smtp",
|
|
2014
|
+
code: "smtp.smtputf8-unsupported",
|
|
2015
|
+
category: "configuration",
|
|
2016
|
+
retryable: false,
|
|
2017
|
+
attempts: 1,
|
|
2018
|
+
providerDetails: { missingCapability: error.missingCapability }
|
|
2019
|
+
});
|
|
1975
2020
|
if (error instanceof SmtpResponseError) {
|
|
1976
2021
|
const classification = classifySmtpReply(error.code);
|
|
1977
2022
|
return (0, __upyo_core.createFailedReceipt)(message, {
|
|
@@ -1993,7 +2038,7 @@ function createSmtpFailure(message, error) {
|
|
|
1993
2038
|
});
|
|
1994
2039
|
}
|
|
1995
2040
|
function isReusableLocalFailure(error) {
|
|
1996
|
-
return error instanceof SmtpMessageSizeError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
|
|
2041
|
+
return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
|
|
1997
2042
|
}
|
|
1998
2043
|
function classifySmtpReply(code) {
|
|
1999
2044
|
if (code >= 400 && code < 500) return {
|
package/dist/index.js
CHANGED
|
@@ -478,6 +478,29 @@ var SmtpMessageSizeError = class extends RangeError {
|
|
|
478
478
|
this.maximumSize = maximumSize;
|
|
479
479
|
}
|
|
480
480
|
};
|
|
481
|
+
/**
|
|
482
|
+
* Error thrown when an internationalized message requires an SMTP extension
|
|
483
|
+
* that the server did not advertise.
|
|
484
|
+
*
|
|
485
|
+
* The check happens before `MAIL FROM`, so the SMTP connection remains usable
|
|
486
|
+
* for another message.
|
|
487
|
+
*
|
|
488
|
+
* @since 0.6.0
|
|
489
|
+
*/
|
|
490
|
+
var SmtpUtf8UnsupportedError = class extends Error {
|
|
491
|
+
/** The required SMTP extension that the server did not advertise. */
|
|
492
|
+
missingCapability;
|
|
493
|
+
/**
|
|
494
|
+
* Creates an SMTPUTF8 support error.
|
|
495
|
+
*
|
|
496
|
+
* @param missingCapability The required extension that was not advertised.
|
|
497
|
+
*/
|
|
498
|
+
constructor(missingCapability) {
|
|
499
|
+
super(missingCapability === "SMTPUTF8" ? "The SMTP server does not advertise SMTPUTF8." : "The SMTP server advertises SMTPUTF8 without the required 8BITMIME capability.");
|
|
500
|
+
this.name = "SmtpUtf8UnsupportedError";
|
|
501
|
+
this.missingCapability = missingCapability;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
481
504
|
var SmtpPipelineTerminatedError = class extends Error {
|
|
482
505
|
responseIndex;
|
|
483
506
|
response;
|
|
@@ -951,6 +974,12 @@ var SmtpConnection = class {
|
|
|
951
974
|
}
|
|
952
975
|
async sendMessage(message, signal) {
|
|
953
976
|
signal?.throwIfAborted();
|
|
977
|
+
let smtpUtf8Parameters = "";
|
|
978
|
+
if (message.requiresSmtpUtf8 === true) {
|
|
979
|
+
if (!this.capabilities.some((capability) => /^SMTPUTF8(?:[ \t]|$)/i.test(capability))) throw new SmtpUtf8UnsupportedError("SMTPUTF8");
|
|
980
|
+
if (!this.capabilities.some((capability) => /^8BITMIME(?:[ \t]|$)/i.test(capability))) throw new SmtpUtf8UnsupportedError("8BITMIME");
|
|
981
|
+
smtpUtf8Parameters = " BODY=8BITMIME SMTPUTF8";
|
|
982
|
+
}
|
|
954
983
|
const sizeCapability = parseSizeCapability(this.capabilities);
|
|
955
984
|
let sizeParameter = "";
|
|
956
985
|
if (sizeCapability != null) {
|
|
@@ -961,7 +990,7 @@ var SmtpConnection = class {
|
|
|
961
990
|
const dsn = message.envelope.dsn;
|
|
962
991
|
if (dsn != null && !this.capabilities.some((capability) => /^DSN[ \t]*$/i.test(capability))) throw new SmtpDsnUnsupportedError();
|
|
963
992
|
const mailDsnParameters = dsn == null || dsn.mailParameters.length === 0 ? "" : ` ${dsn.mailParameters.join(" ")}`;
|
|
964
|
-
const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${mailDsnParameters}`;
|
|
993
|
+
const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
|
|
965
994
|
const recipientCommands = message.envelope.to.map((recipient, index) => {
|
|
966
995
|
const parameters = dsn?.recipientParameters[index] ?? [];
|
|
967
996
|
const suffix = parameters.length === 0 ? "" : ` ${parameters.join(" ")}`;
|
|
@@ -1408,6 +1437,13 @@ async function convertMessage(message, dkimConfig, dsn) {
|
|
|
1408
1437
|
],
|
|
1409
1438
|
dsn
|
|
1410
1439
|
};
|
|
1440
|
+
const requiresSmtpUtf8 = [
|
|
1441
|
+
message.sender,
|
|
1442
|
+
...message.recipients,
|
|
1443
|
+
...message.ccRecipients,
|
|
1444
|
+
...message.bccRecipients,
|
|
1445
|
+
...message.replyRecipients
|
|
1446
|
+
].some((address) => Array.from(address.address).some((character) => (character.codePointAt(0) ?? 0) > 127));
|
|
1411
1447
|
let raw = await buildRawMessage(message);
|
|
1412
1448
|
if (dkimConfig) try {
|
|
1413
1449
|
for (const sig of dkimConfig.signatures) {
|
|
@@ -1420,7 +1456,8 @@ async function convertMessage(message, dkimConfig, dsn) {
|
|
|
1420
1456
|
}
|
|
1421
1457
|
return {
|
|
1422
1458
|
envelope,
|
|
1423
|
-
raw
|
|
1459
|
+
raw,
|
|
1460
|
+
requiresSmtpUtf8
|
|
1424
1461
|
};
|
|
1425
1462
|
}
|
|
1426
1463
|
async function buildRawMessage(message) {
|
|
@@ -1591,7 +1628,7 @@ function foldHeader(name, value) {
|
|
|
1591
1628
|
remaining = remaining.slice(whitespaceEnd);
|
|
1592
1629
|
}
|
|
1593
1630
|
lines.push(prefix + remaining);
|
|
1594
|
-
if (lines.some((line) => line
|
|
1631
|
+
if (lines.some((line) => Buffer.byteLength(line, "utf8") > 998)) throw new RangeError(`Header field ${name} contains a token too long to fold.`);
|
|
1595
1632
|
return lines.join("\r\n");
|
|
1596
1633
|
}
|
|
1597
1634
|
function encodeQuotedPrintable(text) {
|
|
@@ -1949,6 +1986,14 @@ function createSmtpFailure(message, error) {
|
|
|
1949
1986
|
maximumSize: error.maximumSize.toString()
|
|
1950
1987
|
}
|
|
1951
1988
|
});
|
|
1989
|
+
if (error instanceof SmtpUtf8UnsupportedError) return createFailedReceipt(message, {
|
|
1990
|
+
provider: "smtp",
|
|
1991
|
+
code: "smtp.smtputf8-unsupported",
|
|
1992
|
+
category: "configuration",
|
|
1993
|
+
retryable: false,
|
|
1994
|
+
attempts: 1,
|
|
1995
|
+
providerDetails: { missingCapability: error.missingCapability }
|
|
1996
|
+
});
|
|
1952
1997
|
if (error instanceof SmtpResponseError) {
|
|
1953
1998
|
const classification = classifySmtpReply(error.code);
|
|
1954
1999
|
return createFailedReceipt(message, {
|
|
@@ -1970,7 +2015,7 @@ function createSmtpFailure(message, error) {
|
|
|
1970
2015
|
});
|
|
1971
2016
|
}
|
|
1972
2017
|
function isReusableLocalFailure(error) {
|
|
1973
|
-
return error instanceof SmtpMessageSizeError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
|
|
2018
|
+
return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
|
|
1974
2019
|
}
|
|
1975
2020
|
function classifySmtpReply(code) {
|
|
1976
2021
|
if (code >= 400 && code < 500) return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/smtp",
|
|
3
|
-
"version": "0.6.0-dev.
|
|
3
|
+
"version": "0.6.0-dev.296",
|
|
4
4
|
"description": "SMTP transport for Upyo email library",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"sideEffects": false,
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@upyo/core": "0.6.0-dev.
|
|
56
|
+
"@upyo/core": "0.6.0-dev.296+edf6b8c1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"tsdown": "^0.12.7",
|