@upyo/smtp 0.5.2 → 0.5.3
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/index.cjs +128 -36
- package/dist/index.d.cts +38 -3
- package/dist/index.d.ts +38 -3
- package/dist/index.js +128 -36
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -592,6 +592,12 @@ var SmtpConnection = class {
|
|
|
592
592
|
}
|
|
593
593
|
async ehlo(signal) {
|
|
594
594
|
const response = await this.sendCommand(`EHLO ${this.config.localName}`, signal);
|
|
595
|
+
if (response.code === 500 || response.code === 502) {
|
|
596
|
+
const heloResponse = await this.sendCommand(`HELO ${this.config.localName}`, signal);
|
|
597
|
+
if (heloResponse.code !== 250) throw new Error(`HELO failed: ${heloResponse.message}`);
|
|
598
|
+
this.capabilities = [];
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
595
601
|
if (response.code !== 250) throw new Error(`EHLO failed: ${response.message}`);
|
|
596
602
|
this.capabilities = response.raw.split("\r\n").filter((line) => line.startsWith("250-") || line.startsWith("250 ")).map((line) => line.substring(4)).filter((line) => line.length > 0);
|
|
597
603
|
}
|
|
@@ -748,10 +754,22 @@ var SmtpConnection = class {
|
|
|
748
754
|
async sendMessage(message, signal) {
|
|
749
755
|
const mailResponse = await this.sendCommand(`MAIL FROM:<${message.envelope.from}>`, signal);
|
|
750
756
|
if (mailResponse.code !== 250) throw new SmtpResponseError(`MAIL FROM failed: ${mailResponse.message}`, mailResponse.code, "MAIL FROM", mailResponse.message);
|
|
757
|
+
const rejectedRecipients = [];
|
|
751
758
|
for (const recipient of message.envelope.to) {
|
|
752
759
|
signal?.throwIfAborted();
|
|
753
760
|
const rcptResponse = await this.sendCommand(`RCPT TO:<${recipient}>`, signal);
|
|
754
|
-
if (rcptResponse.code
|
|
761
|
+
if (rcptResponse.code === 421) throw new SmtpResponseError(`RCPT TO failed for ${recipient}: ${rcptResponse.message}`, rcptResponse.code, "RCPT TO", rcptResponse.message);
|
|
762
|
+
if (rcptResponse.code !== 250 && rcptResponse.code !== 251) rejectedRecipients.push({
|
|
763
|
+
recipient,
|
|
764
|
+
code: rcptResponse.code,
|
|
765
|
+
response: rcptResponse.message,
|
|
766
|
+
retryable: rcptResponse.code >= 400 && rcptResponse.code < 500
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
if (rejectedRecipients.length > 0 && rejectedRecipients.length === message.envelope.to.length) {
|
|
770
|
+
const rejection = rejectedRecipients.find((item) => item.retryable) ?? rejectedRecipients[0];
|
|
771
|
+
const details = rejectedRecipients.map((item) => `${item.recipient}: ${item.code} ${item.response}`).join("; ");
|
|
772
|
+
throw new SmtpResponseError(`RCPT TO failed for every recipient: ${details}`, rejection.code, "RCPT TO", rejection.response, rejectedRecipients);
|
|
755
773
|
}
|
|
756
774
|
const dataResponse = await this.sendCommand("DATA", signal);
|
|
757
775
|
if (dataResponse.code !== 354) throw new SmtpResponseError(`DATA failed: ${dataResponse.message}`, dataResponse.code, "DATA", dataResponse.message);
|
|
@@ -759,7 +777,10 @@ var SmtpConnection = class {
|
|
|
759
777
|
const finalResponse = await this.sendCommand(`${content}\r\n.`, signal);
|
|
760
778
|
if (finalResponse.code !== 250) throw new SmtpResponseError(`Message send failed: ${finalResponse.message}`, finalResponse.code, "DATA_END", finalResponse.message);
|
|
761
779
|
const messageId = this.extractMessageId(finalResponse.message);
|
|
762
|
-
return
|
|
780
|
+
return {
|
|
781
|
+
messageId,
|
|
782
|
+
rejectedRecipients
|
|
783
|
+
};
|
|
763
784
|
}
|
|
764
785
|
extractMessageId(response) {
|
|
765
786
|
const match = response.match(/(?:Message-ID:|id=)[\s<]*([^>\s]+)/i);
|
|
@@ -815,6 +836,8 @@ var SmtpResponseError = class extends Error {
|
|
|
815
836
|
* The textual SMTP reply returned by the server.
|
|
816
837
|
*/
|
|
817
838
|
response;
|
|
839
|
+
/** Recipient-level failures collected for an unsuccessful transaction. */
|
|
840
|
+
rejectedRecipients;
|
|
818
841
|
/**
|
|
819
842
|
* Creates an SMTP response error.
|
|
820
843
|
*
|
|
@@ -822,13 +845,16 @@ var SmtpResponseError = class extends Error {
|
|
|
822
845
|
* @param code The numeric SMTP reply code.
|
|
823
846
|
* @param command The SMTP command that produced the reply.
|
|
824
847
|
* @param response The textual SMTP reply returned by the server.
|
|
848
|
+
* @param rejectedRecipients Recipient-level failures collected for the
|
|
849
|
+
* transaction.
|
|
825
850
|
*/
|
|
826
|
-
constructor(message, code, command, response) {
|
|
851
|
+
constructor(message, code, command, response, rejectedRecipients) {
|
|
827
852
|
super(message);
|
|
828
853
|
this.name = "SmtpResponseError";
|
|
829
854
|
this.code = code;
|
|
830
855
|
this.command = command;
|
|
831
856
|
this.response = response;
|
|
857
|
+
this.rejectedRecipients = rejectedRecipients;
|
|
832
858
|
}
|
|
833
859
|
};
|
|
834
860
|
/**
|
|
@@ -1130,6 +1156,15 @@ function arrayBufferToBase64(buffer) {
|
|
|
1130
1156
|
|
|
1131
1157
|
//#endregion
|
|
1132
1158
|
//#region src/message-converter.ts
|
|
1159
|
+
/**
|
|
1160
|
+
* Converts a message to its SMTP envelope and wire representation.
|
|
1161
|
+
*
|
|
1162
|
+
* @param message The message to convert.
|
|
1163
|
+
* @param dkimConfig Optional DKIM signing configuration.
|
|
1164
|
+
* @returns The converted SMTP message.
|
|
1165
|
+
* @throws {RangeError} If a header contains a token that cannot be folded
|
|
1166
|
+
* within the RFC 5322 hard line-length limit.
|
|
1167
|
+
*/
|
|
1133
1168
|
async function convertMessage(message, dkimConfig) {
|
|
1134
1169
|
const envelope = {
|
|
1135
1170
|
from: message.sender.address,
|
|
@@ -1161,11 +1196,11 @@ async function buildRawMessage(message) {
|
|
|
1161
1196
|
const hasHtml = "html" in message.content;
|
|
1162
1197
|
const hasText = "text" in message.content;
|
|
1163
1198
|
const isMultipart = hasAttachments || hasHtml && hasText;
|
|
1164
|
-
lines.push(
|
|
1165
|
-
lines.push(
|
|
1166
|
-
if (message.ccRecipients.length > 0) lines.push(
|
|
1167
|
-
if (message.replyRecipients.length > 0) lines.push(
|
|
1168
|
-
lines.push(
|
|
1199
|
+
lines.push(foldHeader("From", encodeAddress(message.sender)));
|
|
1200
|
+
lines.push(foldHeader("To", message.recipients.map(encodeAddress).join(", ")));
|
|
1201
|
+
if (message.ccRecipients.length > 0) lines.push(foldHeader("Cc", message.ccRecipients.map(encodeAddress).join(", ")));
|
|
1202
|
+
if (message.replyRecipients.length > 0) lines.push(foldHeader("Reply-To", message.replyRecipients.map(encodeAddress).join(", ")));
|
|
1203
|
+
lines.push(foldHeader("Subject", encodeHeaderValue(message.subject, true)));
|
|
1169
1204
|
lines.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1170
1205
|
lines.push(`Message-ID: <${generateMessageId()}>`);
|
|
1171
1206
|
if (message.priority !== "normal") {
|
|
@@ -1173,7 +1208,7 @@ async function buildRawMessage(message) {
|
|
|
1173
1208
|
lines.push(`X-Priority: ${priorityValue}`);
|
|
1174
1209
|
lines.push(`X-MSMail-Priority: ${message.priority === "high" ? "High" : "Low"}`);
|
|
1175
1210
|
}
|
|
1176
|
-
for (const [key, value] of message.headers) lines.push(
|
|
1211
|
+
for (const [key, value] of message.headers) lines.push(foldHeader(key, encodeHeaderValue(value)));
|
|
1177
1212
|
lines.push("MIME-Version: 1.0");
|
|
1178
1213
|
if (isMultipart) {
|
|
1179
1214
|
lines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
@@ -1212,12 +1247,12 @@ async function buildRawMessage(message) {
|
|
|
1212
1247
|
for (const attachment of message.attachments) {
|
|
1213
1248
|
lines.push("");
|
|
1214
1249
|
lines.push(`--${boundary}`);
|
|
1215
|
-
lines.push(
|
|
1250
|
+
lines.push(foldHeader("Content-Type", `${attachment.contentType}; ${encodeMimeParameter("name", attachment.filename)}`));
|
|
1216
1251
|
lines.push("Content-Transfer-Encoding: base64");
|
|
1217
1252
|
if (attachment.inline) {
|
|
1218
|
-
lines.push(
|
|
1253
|
+
lines.push(foldHeader("Content-Disposition", `inline; ${encodeMimeParameter("filename", attachment.filename)}`));
|
|
1219
1254
|
lines.push(`Content-ID: <${attachment.contentId}>`);
|
|
1220
|
-
} else lines.push(
|
|
1255
|
+
} else lines.push(foldHeader("Content-Disposition", `attachment; ${encodeMimeParameter("filename", attachment.filename)}`));
|
|
1221
1256
|
lines.push("");
|
|
1222
1257
|
lines.push(encodeBase64(await attachment.content));
|
|
1223
1258
|
}
|
|
@@ -1246,32 +1281,85 @@ function generateMessageId() {
|
|
|
1246
1281
|
}
|
|
1247
1282
|
function encodeAddress(address) {
|
|
1248
1283
|
if (address.name == null) return address.address;
|
|
1249
|
-
const encodedDisplayName = encodeHeaderValue(address.name);
|
|
1284
|
+
const encodedDisplayName = encodeHeaderValue(address.name, true);
|
|
1250
1285
|
return `${encodedDisplayName} <${address.address}>`;
|
|
1251
1286
|
}
|
|
1252
|
-
function encodeHeaderValue(value) {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const
|
|
1287
|
+
function encodeHeaderValue(value, encodeLongAsciiWords = false) {
|
|
1288
|
+
const hasLongWord = value.split(/\s+/).some((word) => word.length > 60);
|
|
1289
|
+
if (!/^[\x20-\x7E]*$/.test(value) || encodeLongAsciiWords && hasLongWord) {
|
|
1290
|
+
const encodeWord = (text) => {
|
|
1291
|
+
const utf8Bytes = new TextEncoder().encode(text);
|
|
1292
|
+
const base64 = node_buffer.Buffer.from(utf8Bytes).toString("base64");
|
|
1293
|
+
return `=?UTF-8?B?${base64}?=`;
|
|
1294
|
+
};
|
|
1256
1295
|
const maxEncodedLength = 75;
|
|
1257
|
-
const encodedWord =
|
|
1296
|
+
const encodedWord = encodeWord(value);
|
|
1258
1297
|
if (encodedWord.length <= maxEncodedLength) return encodedWord;
|
|
1259
1298
|
const words = [];
|
|
1260
|
-
let
|
|
1261
|
-
for (
|
|
1262
|
-
const
|
|
1263
|
-
|
|
1264
|
-
if (testWord.length <= maxEncodedLength) currentBase64 += chunk;
|
|
1299
|
+
let currentText = "";
|
|
1300
|
+
for (const character of value) {
|
|
1301
|
+
const candidate = currentText + character;
|
|
1302
|
+
if (encodeWord(candidate).length <= maxEncodedLength) currentText = candidate;
|
|
1265
1303
|
else {
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1304
|
+
if (currentText.length > 0) words.push(encodeWord(currentText));
|
|
1305
|
+
currentText = character;
|
|
1268
1306
|
}
|
|
1269
1307
|
}
|
|
1270
|
-
if (
|
|
1308
|
+
if (currentText.length > 0) words.push(encodeWord(currentText));
|
|
1271
1309
|
return words.join(" ");
|
|
1272
1310
|
}
|
|
1273
1311
|
return value;
|
|
1274
1312
|
}
|
|
1313
|
+
function encodeMimeParameter(name, value) {
|
|
1314
|
+
const escapedValue = value.replace(/[\\"]/g, "\\$&");
|
|
1315
|
+
const quotedParameter = `${name}="${escapedValue}"`;
|
|
1316
|
+
if (/^[\x20-\x7E]*$/.test(value) && quotedParameter.length <= 60) return quotedParameter;
|
|
1317
|
+
const encodedBytes = Array.from(new TextEncoder().encode(value), (byte) => {
|
|
1318
|
+
const character = String.fromCharCode(byte);
|
|
1319
|
+
return /^[A-Za-z0-9!#$&+.^_`|~-]$/.test(character) ? character : `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
1320
|
+
});
|
|
1321
|
+
const segments = [];
|
|
1322
|
+
let segment = "";
|
|
1323
|
+
for (const encodedByte of encodedBytes) {
|
|
1324
|
+
if (segment.length + encodedByte.length > 45) {
|
|
1325
|
+
segments.push(segment);
|
|
1326
|
+
segment = "";
|
|
1327
|
+
}
|
|
1328
|
+
segment += encodedByte;
|
|
1329
|
+
}
|
|
1330
|
+
if (segment.length > 0 || segments.length === 0) segments.push(segment);
|
|
1331
|
+
return segments.map((part, index) => `${name}*${index}*=${index === 0 ? "UTF-8''" : ""}${part}`).join("; ");
|
|
1332
|
+
}
|
|
1333
|
+
function foldHeader(name, value) {
|
|
1334
|
+
const recommendedLineLength = 78;
|
|
1335
|
+
const lines = [];
|
|
1336
|
+
let prefix = `${name}: `;
|
|
1337
|
+
let remaining = value;
|
|
1338
|
+
while (prefix.length + remaining.length > recommendedLineLength) {
|
|
1339
|
+
const availableLength = recommendedLineLength - prefix.length;
|
|
1340
|
+
let breakIndex = -1;
|
|
1341
|
+
for (let index = Math.min(availableLength, remaining.length - 1); index >= 0; index--) if (remaining[index] === " " || remaining[index] === " ") {
|
|
1342
|
+
breakIndex = index;
|
|
1343
|
+
break;
|
|
1344
|
+
}
|
|
1345
|
+
if (breakIndex < 0) {
|
|
1346
|
+
for (let index = Math.max(availableLength + 1, 0); index < remaining.length; index++) if (remaining[index] === " " || remaining[index] === " ") {
|
|
1347
|
+
breakIndex = index;
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
if (breakIndex < 0) break;
|
|
1352
|
+
let whitespaceEnd = breakIndex + 1;
|
|
1353
|
+
while (whitespaceEnd < remaining.length && (remaining[whitespaceEnd] === " " || remaining[whitespaceEnd] === " ")) whitespaceEnd++;
|
|
1354
|
+
if (whitespaceEnd === remaining.length) break;
|
|
1355
|
+
lines.push(prefix + remaining.slice(0, breakIndex));
|
|
1356
|
+
prefix = remaining.slice(breakIndex, whitespaceEnd);
|
|
1357
|
+
remaining = remaining.slice(whitespaceEnd);
|
|
1358
|
+
}
|
|
1359
|
+
lines.push(prefix + remaining);
|
|
1360
|
+
if (lines.some((line) => line.length > 998)) throw new RangeError(`Header field ${name} contains a token too long to fold.`);
|
|
1361
|
+
return lines.join("\r\n");
|
|
1362
|
+
}
|
|
1275
1363
|
function encodeQuotedPrintable(text) {
|
|
1276
1364
|
const utf8Bytes = new TextEncoder().encode(text);
|
|
1277
1365
|
let result = "";
|
|
@@ -1402,12 +1490,13 @@ var SmtpTransport = class {
|
|
|
1402
1490
|
options?.signal?.throwIfAborted();
|
|
1403
1491
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1404
1492
|
options?.signal?.throwIfAborted();
|
|
1405
|
-
const
|
|
1493
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1406
1494
|
await this.returnConnection(connection);
|
|
1407
1495
|
return {
|
|
1408
1496
|
successful: true,
|
|
1409
|
-
messageId,
|
|
1410
|
-
provider: "smtp"
|
|
1497
|
+
messageId: result.messageId,
|
|
1498
|
+
provider: "smtp",
|
|
1499
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1411
1500
|
};
|
|
1412
1501
|
} catch (error) {
|
|
1413
1502
|
if (connection != null) await this.discardConnection(connection);
|
|
@@ -1471,11 +1560,12 @@ var SmtpTransport = class {
|
|
|
1471
1560
|
try {
|
|
1472
1561
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1473
1562
|
options?.signal?.throwIfAborted();
|
|
1474
|
-
const
|
|
1563
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1475
1564
|
yield {
|
|
1476
1565
|
successful: true,
|
|
1477
|
-
messageId,
|
|
1478
|
-
provider: "smtp"
|
|
1566
|
+
messageId: result.messageId,
|
|
1567
|
+
provider: "smtp",
|
|
1568
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1479
1569
|
};
|
|
1480
1570
|
} catch (error) {
|
|
1481
1571
|
options?.signal?.throwIfAborted();
|
|
@@ -1492,11 +1582,12 @@ var SmtpTransport = class {
|
|
|
1492
1582
|
try {
|
|
1493
1583
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1494
1584
|
options?.signal?.throwIfAborted();
|
|
1495
|
-
const
|
|
1585
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1496
1586
|
yield {
|
|
1497
1587
|
successful: true,
|
|
1498
|
-
messageId,
|
|
1499
|
-
provider: "smtp"
|
|
1588
|
+
messageId: result.messageId,
|
|
1589
|
+
provider: "smtp",
|
|
1590
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1500
1591
|
};
|
|
1501
1592
|
} catch (error) {
|
|
1502
1593
|
options?.signal?.throwIfAborted();
|
|
@@ -1605,7 +1696,8 @@ function createSmtpFailure(message, error) {
|
|
|
1605
1696
|
attempts: 1,
|
|
1606
1697
|
providerDetails: {
|
|
1607
1698
|
command: error.command,
|
|
1608
|
-
response: error.response
|
|
1699
|
+
response: error.response,
|
|
1700
|
+
rejectedRecipients: error.rejectedRecipients
|
|
1609
1701
|
}
|
|
1610
1702
|
});
|
|
1611
1703
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -408,6 +408,41 @@ interface SmtpTlsOptions {
|
|
|
408
408
|
* used internally by the SMTP transport implementation.
|
|
409
409
|
*/
|
|
410
410
|
//#endregion
|
|
411
|
+
//#region src/smtp-receipt.d.ts
|
|
412
|
+
/**
|
|
413
|
+
* An SMTP envelope recipient that the server rejected during an otherwise
|
|
414
|
+
* successful delivery.
|
|
415
|
+
*
|
|
416
|
+
* @since 0.5.3
|
|
417
|
+
*/
|
|
418
|
+
interface SmtpRejectedRecipient {
|
|
419
|
+
/** The rejected recipient address. */
|
|
420
|
+
readonly recipient: string;
|
|
421
|
+
/** The three-digit SMTP reply code. */
|
|
422
|
+
readonly code: number;
|
|
423
|
+
/** The SMTP server's reply text. */
|
|
424
|
+
readonly response: string;
|
|
425
|
+
/** Whether retrying delivery to this recipient may succeed. */
|
|
426
|
+
readonly retryable: boolean;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* A receipt returned by {@link SmtpTransport}.
|
|
430
|
+
*
|
|
431
|
+
* Successful receipts list any recipients rejected before the message was
|
|
432
|
+
* delivered to the remaining accepted recipients. Callers can retry delivery
|
|
433
|
+
* to entries marked as retryable without redelivering to accepted recipients.
|
|
434
|
+
*
|
|
435
|
+
* @since 0.5.3
|
|
436
|
+
*/
|
|
437
|
+
type SmtpReceipt = (Extract<Receipt<"smtp">, {
|
|
438
|
+
readonly successful: true;
|
|
439
|
+
}> & {
|
|
440
|
+
/** Recipients excluded from an otherwise successful delivery. */
|
|
441
|
+
readonly rejectedRecipients: readonly SmtpRejectedRecipient[];
|
|
442
|
+
}) | Extract<Receipt<"smtp">, {
|
|
443
|
+
readonly successful: false;
|
|
444
|
+
}>;
|
|
445
|
+
//#endregion
|
|
411
446
|
//#region src/smtp-transport.d.ts
|
|
412
447
|
/**
|
|
413
448
|
* SMTP transport implementation for sending emails via SMTP protocol.
|
|
@@ -495,7 +530,7 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
|
|
|
495
530
|
* @throws {DOMException} If the operation is aborted through
|
|
496
531
|
* `options.signal`.
|
|
497
532
|
*/
|
|
498
|
-
send(message: Message, options?: TransportOptions): Promise<
|
|
533
|
+
send(message: Message, options?: TransportOptions): Promise<SmtpReceipt>;
|
|
499
534
|
/**
|
|
500
535
|
* Sends multiple email messages efficiently using a single SMTP connection.
|
|
501
536
|
*
|
|
@@ -526,7 +561,7 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
|
|
|
526
561
|
* @throws {DOMException} If the operation is aborted through
|
|
527
562
|
* `options.signal`.
|
|
528
563
|
*/
|
|
529
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<
|
|
564
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<SmtpReceipt>;
|
|
530
565
|
private getConnection;
|
|
531
566
|
private connectAndSetup;
|
|
532
567
|
private returnConnection;
|
|
@@ -591,4 +626,4 @@ declare class SmtpAuthError extends Error {
|
|
|
591
626
|
*/
|
|
592
627
|
|
|
593
628
|
//#endregion
|
|
594
|
-
export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpTlsOptions, SmtpTransport, SmtpUserPassAuth };
|
|
629
|
+
export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpReceipt, SmtpRejectedRecipient, SmtpTlsOptions, SmtpTransport, SmtpUserPassAuth };
|
package/dist/index.d.ts
CHANGED
|
@@ -408,6 +408,41 @@ interface SmtpTlsOptions {
|
|
|
408
408
|
* used internally by the SMTP transport implementation.
|
|
409
409
|
*/
|
|
410
410
|
//#endregion
|
|
411
|
+
//#region src/smtp-receipt.d.ts
|
|
412
|
+
/**
|
|
413
|
+
* An SMTP envelope recipient that the server rejected during an otherwise
|
|
414
|
+
* successful delivery.
|
|
415
|
+
*
|
|
416
|
+
* @since 0.5.3
|
|
417
|
+
*/
|
|
418
|
+
interface SmtpRejectedRecipient {
|
|
419
|
+
/** The rejected recipient address. */
|
|
420
|
+
readonly recipient: string;
|
|
421
|
+
/** The three-digit SMTP reply code. */
|
|
422
|
+
readonly code: number;
|
|
423
|
+
/** The SMTP server's reply text. */
|
|
424
|
+
readonly response: string;
|
|
425
|
+
/** Whether retrying delivery to this recipient may succeed. */
|
|
426
|
+
readonly retryable: boolean;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* A receipt returned by {@link SmtpTransport}.
|
|
430
|
+
*
|
|
431
|
+
* Successful receipts list any recipients rejected before the message was
|
|
432
|
+
* delivered to the remaining accepted recipients. Callers can retry delivery
|
|
433
|
+
* to entries marked as retryable without redelivering to accepted recipients.
|
|
434
|
+
*
|
|
435
|
+
* @since 0.5.3
|
|
436
|
+
*/
|
|
437
|
+
type SmtpReceipt = (Extract<Receipt<"smtp">, {
|
|
438
|
+
readonly successful: true;
|
|
439
|
+
}> & {
|
|
440
|
+
/** Recipients excluded from an otherwise successful delivery. */
|
|
441
|
+
readonly rejectedRecipients: readonly SmtpRejectedRecipient[];
|
|
442
|
+
}) | Extract<Receipt<"smtp">, {
|
|
443
|
+
readonly successful: false;
|
|
444
|
+
}>;
|
|
445
|
+
//#endregion
|
|
411
446
|
//#region src/smtp-transport.d.ts
|
|
412
447
|
/**
|
|
413
448
|
* SMTP transport implementation for sending emails via SMTP protocol.
|
|
@@ -495,7 +530,7 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
|
|
|
495
530
|
* @throws {DOMException} If the operation is aborted through
|
|
496
531
|
* `options.signal`.
|
|
497
532
|
*/
|
|
498
|
-
send(message: Message, options?: TransportOptions): Promise<
|
|
533
|
+
send(message: Message, options?: TransportOptions): Promise<SmtpReceipt>;
|
|
499
534
|
/**
|
|
500
535
|
* Sends multiple email messages efficiently using a single SMTP connection.
|
|
501
536
|
*
|
|
@@ -526,7 +561,7 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
|
|
|
526
561
|
* @throws {DOMException} If the operation is aborted through
|
|
527
562
|
* `options.signal`.
|
|
528
563
|
*/
|
|
529
|
-
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<
|
|
564
|
+
sendMany(messages: Iterable<Message> | AsyncIterable<Message>, options?: TransportOptions): AsyncIterable<SmtpReceipt>;
|
|
530
565
|
private getConnection;
|
|
531
566
|
private connectAndSetup;
|
|
532
567
|
private returnConnection;
|
|
@@ -591,4 +626,4 @@ declare class SmtpAuthError extends Error {
|
|
|
591
626
|
*/
|
|
592
627
|
|
|
593
628
|
//#endregion
|
|
594
|
-
export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpTlsOptions, SmtpTransport, SmtpUserPassAuth };
|
|
629
|
+
export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpReceipt, SmtpRejectedRecipient, SmtpTlsOptions, SmtpTransport, SmtpUserPassAuth };
|
package/dist/index.js
CHANGED
|
@@ -569,6 +569,12 @@ var SmtpConnection = class {
|
|
|
569
569
|
}
|
|
570
570
|
async ehlo(signal) {
|
|
571
571
|
const response = await this.sendCommand(`EHLO ${this.config.localName}`, signal);
|
|
572
|
+
if (response.code === 500 || response.code === 502) {
|
|
573
|
+
const heloResponse = await this.sendCommand(`HELO ${this.config.localName}`, signal);
|
|
574
|
+
if (heloResponse.code !== 250) throw new Error(`HELO failed: ${heloResponse.message}`);
|
|
575
|
+
this.capabilities = [];
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
572
578
|
if (response.code !== 250) throw new Error(`EHLO failed: ${response.message}`);
|
|
573
579
|
this.capabilities = response.raw.split("\r\n").filter((line) => line.startsWith("250-") || line.startsWith("250 ")).map((line) => line.substring(4)).filter((line) => line.length > 0);
|
|
574
580
|
}
|
|
@@ -725,10 +731,22 @@ var SmtpConnection = class {
|
|
|
725
731
|
async sendMessage(message, signal) {
|
|
726
732
|
const mailResponse = await this.sendCommand(`MAIL FROM:<${message.envelope.from}>`, signal);
|
|
727
733
|
if (mailResponse.code !== 250) throw new SmtpResponseError(`MAIL FROM failed: ${mailResponse.message}`, mailResponse.code, "MAIL FROM", mailResponse.message);
|
|
734
|
+
const rejectedRecipients = [];
|
|
728
735
|
for (const recipient of message.envelope.to) {
|
|
729
736
|
signal?.throwIfAborted();
|
|
730
737
|
const rcptResponse = await this.sendCommand(`RCPT TO:<${recipient}>`, signal);
|
|
731
|
-
if (rcptResponse.code
|
|
738
|
+
if (rcptResponse.code === 421) throw new SmtpResponseError(`RCPT TO failed for ${recipient}: ${rcptResponse.message}`, rcptResponse.code, "RCPT TO", rcptResponse.message);
|
|
739
|
+
if (rcptResponse.code !== 250 && rcptResponse.code !== 251) rejectedRecipients.push({
|
|
740
|
+
recipient,
|
|
741
|
+
code: rcptResponse.code,
|
|
742
|
+
response: rcptResponse.message,
|
|
743
|
+
retryable: rcptResponse.code >= 400 && rcptResponse.code < 500
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (rejectedRecipients.length > 0 && rejectedRecipients.length === message.envelope.to.length) {
|
|
747
|
+
const rejection = rejectedRecipients.find((item) => item.retryable) ?? rejectedRecipients[0];
|
|
748
|
+
const details = rejectedRecipients.map((item) => `${item.recipient}: ${item.code} ${item.response}`).join("; ");
|
|
749
|
+
throw new SmtpResponseError(`RCPT TO failed for every recipient: ${details}`, rejection.code, "RCPT TO", rejection.response, rejectedRecipients);
|
|
732
750
|
}
|
|
733
751
|
const dataResponse = await this.sendCommand("DATA", signal);
|
|
734
752
|
if (dataResponse.code !== 354) throw new SmtpResponseError(`DATA failed: ${dataResponse.message}`, dataResponse.code, "DATA", dataResponse.message);
|
|
@@ -736,7 +754,10 @@ var SmtpConnection = class {
|
|
|
736
754
|
const finalResponse = await this.sendCommand(`${content}\r\n.`, signal);
|
|
737
755
|
if (finalResponse.code !== 250) throw new SmtpResponseError(`Message send failed: ${finalResponse.message}`, finalResponse.code, "DATA_END", finalResponse.message);
|
|
738
756
|
const messageId = this.extractMessageId(finalResponse.message);
|
|
739
|
-
return
|
|
757
|
+
return {
|
|
758
|
+
messageId,
|
|
759
|
+
rejectedRecipients
|
|
760
|
+
};
|
|
740
761
|
}
|
|
741
762
|
extractMessageId(response) {
|
|
742
763
|
const match = response.match(/(?:Message-ID:|id=)[\s<]*([^>\s]+)/i);
|
|
@@ -792,6 +813,8 @@ var SmtpResponseError = class extends Error {
|
|
|
792
813
|
* The textual SMTP reply returned by the server.
|
|
793
814
|
*/
|
|
794
815
|
response;
|
|
816
|
+
/** Recipient-level failures collected for an unsuccessful transaction. */
|
|
817
|
+
rejectedRecipients;
|
|
795
818
|
/**
|
|
796
819
|
* Creates an SMTP response error.
|
|
797
820
|
*
|
|
@@ -799,13 +822,16 @@ var SmtpResponseError = class extends Error {
|
|
|
799
822
|
* @param code The numeric SMTP reply code.
|
|
800
823
|
* @param command The SMTP command that produced the reply.
|
|
801
824
|
* @param response The textual SMTP reply returned by the server.
|
|
825
|
+
* @param rejectedRecipients Recipient-level failures collected for the
|
|
826
|
+
* transaction.
|
|
802
827
|
*/
|
|
803
|
-
constructor(message, code, command, response) {
|
|
828
|
+
constructor(message, code, command, response, rejectedRecipients) {
|
|
804
829
|
super(message);
|
|
805
830
|
this.name = "SmtpResponseError";
|
|
806
831
|
this.code = code;
|
|
807
832
|
this.command = command;
|
|
808
833
|
this.response = response;
|
|
834
|
+
this.rejectedRecipients = rejectedRecipients;
|
|
809
835
|
}
|
|
810
836
|
};
|
|
811
837
|
/**
|
|
@@ -1107,6 +1133,15 @@ function arrayBufferToBase64(buffer) {
|
|
|
1107
1133
|
|
|
1108
1134
|
//#endregion
|
|
1109
1135
|
//#region src/message-converter.ts
|
|
1136
|
+
/**
|
|
1137
|
+
* Converts a message to its SMTP envelope and wire representation.
|
|
1138
|
+
*
|
|
1139
|
+
* @param message The message to convert.
|
|
1140
|
+
* @param dkimConfig Optional DKIM signing configuration.
|
|
1141
|
+
* @returns The converted SMTP message.
|
|
1142
|
+
* @throws {RangeError} If a header contains a token that cannot be folded
|
|
1143
|
+
* within the RFC 5322 hard line-length limit.
|
|
1144
|
+
*/
|
|
1110
1145
|
async function convertMessage(message, dkimConfig) {
|
|
1111
1146
|
const envelope = {
|
|
1112
1147
|
from: message.sender.address,
|
|
@@ -1138,11 +1173,11 @@ async function buildRawMessage(message) {
|
|
|
1138
1173
|
const hasHtml = "html" in message.content;
|
|
1139
1174
|
const hasText = "text" in message.content;
|
|
1140
1175
|
const isMultipart = hasAttachments || hasHtml && hasText;
|
|
1141
|
-
lines.push(
|
|
1142
|
-
lines.push(
|
|
1143
|
-
if (message.ccRecipients.length > 0) lines.push(
|
|
1144
|
-
if (message.replyRecipients.length > 0) lines.push(
|
|
1145
|
-
lines.push(
|
|
1176
|
+
lines.push(foldHeader("From", encodeAddress(message.sender)));
|
|
1177
|
+
lines.push(foldHeader("To", message.recipients.map(encodeAddress).join(", ")));
|
|
1178
|
+
if (message.ccRecipients.length > 0) lines.push(foldHeader("Cc", message.ccRecipients.map(encodeAddress).join(", ")));
|
|
1179
|
+
if (message.replyRecipients.length > 0) lines.push(foldHeader("Reply-To", message.replyRecipients.map(encodeAddress).join(", ")));
|
|
1180
|
+
lines.push(foldHeader("Subject", encodeHeaderValue(message.subject, true)));
|
|
1146
1181
|
lines.push(`Date: ${(/* @__PURE__ */ new Date()).toUTCString()}`);
|
|
1147
1182
|
lines.push(`Message-ID: <${generateMessageId()}>`);
|
|
1148
1183
|
if (message.priority !== "normal") {
|
|
@@ -1150,7 +1185,7 @@ async function buildRawMessage(message) {
|
|
|
1150
1185
|
lines.push(`X-Priority: ${priorityValue}`);
|
|
1151
1186
|
lines.push(`X-MSMail-Priority: ${message.priority === "high" ? "High" : "Low"}`);
|
|
1152
1187
|
}
|
|
1153
|
-
for (const [key, value] of message.headers) lines.push(
|
|
1188
|
+
for (const [key, value] of message.headers) lines.push(foldHeader(key, encodeHeaderValue(value)));
|
|
1154
1189
|
lines.push("MIME-Version: 1.0");
|
|
1155
1190
|
if (isMultipart) {
|
|
1156
1191
|
lines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
@@ -1189,12 +1224,12 @@ async function buildRawMessage(message) {
|
|
|
1189
1224
|
for (const attachment of message.attachments) {
|
|
1190
1225
|
lines.push("");
|
|
1191
1226
|
lines.push(`--${boundary}`);
|
|
1192
|
-
lines.push(
|
|
1227
|
+
lines.push(foldHeader("Content-Type", `${attachment.contentType}; ${encodeMimeParameter("name", attachment.filename)}`));
|
|
1193
1228
|
lines.push("Content-Transfer-Encoding: base64");
|
|
1194
1229
|
if (attachment.inline) {
|
|
1195
|
-
lines.push(
|
|
1230
|
+
lines.push(foldHeader("Content-Disposition", `inline; ${encodeMimeParameter("filename", attachment.filename)}`));
|
|
1196
1231
|
lines.push(`Content-ID: <${attachment.contentId}>`);
|
|
1197
|
-
} else lines.push(
|
|
1232
|
+
} else lines.push(foldHeader("Content-Disposition", `attachment; ${encodeMimeParameter("filename", attachment.filename)}`));
|
|
1198
1233
|
lines.push("");
|
|
1199
1234
|
lines.push(encodeBase64(await attachment.content));
|
|
1200
1235
|
}
|
|
@@ -1223,32 +1258,85 @@ function generateMessageId() {
|
|
|
1223
1258
|
}
|
|
1224
1259
|
function encodeAddress(address) {
|
|
1225
1260
|
if (address.name == null) return address.address;
|
|
1226
|
-
const encodedDisplayName = encodeHeaderValue(address.name);
|
|
1261
|
+
const encodedDisplayName = encodeHeaderValue(address.name, true);
|
|
1227
1262
|
return `${encodedDisplayName} <${address.address}>`;
|
|
1228
1263
|
}
|
|
1229
|
-
function encodeHeaderValue(value) {
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
const
|
|
1264
|
+
function encodeHeaderValue(value, encodeLongAsciiWords = false) {
|
|
1265
|
+
const hasLongWord = value.split(/\s+/).some((word) => word.length > 60);
|
|
1266
|
+
if (!/^[\x20-\x7E]*$/.test(value) || encodeLongAsciiWords && hasLongWord) {
|
|
1267
|
+
const encodeWord = (text) => {
|
|
1268
|
+
const utf8Bytes = new TextEncoder().encode(text);
|
|
1269
|
+
const base64 = Buffer.from(utf8Bytes).toString("base64");
|
|
1270
|
+
return `=?UTF-8?B?${base64}?=`;
|
|
1271
|
+
};
|
|
1233
1272
|
const maxEncodedLength = 75;
|
|
1234
|
-
const encodedWord =
|
|
1273
|
+
const encodedWord = encodeWord(value);
|
|
1235
1274
|
if (encodedWord.length <= maxEncodedLength) return encodedWord;
|
|
1236
1275
|
const words = [];
|
|
1237
|
-
let
|
|
1238
|
-
for (
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
if (testWord.length <= maxEncodedLength) currentBase64 += chunk;
|
|
1276
|
+
let currentText = "";
|
|
1277
|
+
for (const character of value) {
|
|
1278
|
+
const candidate = currentText + character;
|
|
1279
|
+
if (encodeWord(candidate).length <= maxEncodedLength) currentText = candidate;
|
|
1242
1280
|
else {
|
|
1243
|
-
if (
|
|
1244
|
-
|
|
1281
|
+
if (currentText.length > 0) words.push(encodeWord(currentText));
|
|
1282
|
+
currentText = character;
|
|
1245
1283
|
}
|
|
1246
1284
|
}
|
|
1247
|
-
if (
|
|
1285
|
+
if (currentText.length > 0) words.push(encodeWord(currentText));
|
|
1248
1286
|
return words.join(" ");
|
|
1249
1287
|
}
|
|
1250
1288
|
return value;
|
|
1251
1289
|
}
|
|
1290
|
+
function encodeMimeParameter(name, value) {
|
|
1291
|
+
const escapedValue = value.replace(/[\\"]/g, "\\$&");
|
|
1292
|
+
const quotedParameter = `${name}="${escapedValue}"`;
|
|
1293
|
+
if (/^[\x20-\x7E]*$/.test(value) && quotedParameter.length <= 60) return quotedParameter;
|
|
1294
|
+
const encodedBytes = Array.from(new TextEncoder().encode(value), (byte) => {
|
|
1295
|
+
const character = String.fromCharCode(byte);
|
|
1296
|
+
return /^[A-Za-z0-9!#$&+.^_`|~-]$/.test(character) ? character : `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
1297
|
+
});
|
|
1298
|
+
const segments = [];
|
|
1299
|
+
let segment = "";
|
|
1300
|
+
for (const encodedByte of encodedBytes) {
|
|
1301
|
+
if (segment.length + encodedByte.length > 45) {
|
|
1302
|
+
segments.push(segment);
|
|
1303
|
+
segment = "";
|
|
1304
|
+
}
|
|
1305
|
+
segment += encodedByte;
|
|
1306
|
+
}
|
|
1307
|
+
if (segment.length > 0 || segments.length === 0) segments.push(segment);
|
|
1308
|
+
return segments.map((part, index) => `${name}*${index}*=${index === 0 ? "UTF-8''" : ""}${part}`).join("; ");
|
|
1309
|
+
}
|
|
1310
|
+
function foldHeader(name, value) {
|
|
1311
|
+
const recommendedLineLength = 78;
|
|
1312
|
+
const lines = [];
|
|
1313
|
+
let prefix = `${name}: `;
|
|
1314
|
+
let remaining = value;
|
|
1315
|
+
while (prefix.length + remaining.length > recommendedLineLength) {
|
|
1316
|
+
const availableLength = recommendedLineLength - prefix.length;
|
|
1317
|
+
let breakIndex = -1;
|
|
1318
|
+
for (let index = Math.min(availableLength, remaining.length - 1); index >= 0; index--) if (remaining[index] === " " || remaining[index] === " ") {
|
|
1319
|
+
breakIndex = index;
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
if (breakIndex < 0) {
|
|
1323
|
+
for (let index = Math.max(availableLength + 1, 0); index < remaining.length; index++) if (remaining[index] === " " || remaining[index] === " ") {
|
|
1324
|
+
breakIndex = index;
|
|
1325
|
+
break;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
if (breakIndex < 0) break;
|
|
1329
|
+
let whitespaceEnd = breakIndex + 1;
|
|
1330
|
+
while (whitespaceEnd < remaining.length && (remaining[whitespaceEnd] === " " || remaining[whitespaceEnd] === " ")) whitespaceEnd++;
|
|
1331
|
+
if (whitespaceEnd === remaining.length) break;
|
|
1332
|
+
lines.push(prefix + remaining.slice(0, breakIndex));
|
|
1333
|
+
prefix = remaining.slice(breakIndex, whitespaceEnd);
|
|
1334
|
+
remaining = remaining.slice(whitespaceEnd);
|
|
1335
|
+
}
|
|
1336
|
+
lines.push(prefix + remaining);
|
|
1337
|
+
if (lines.some((line) => line.length > 998)) throw new RangeError(`Header field ${name} contains a token too long to fold.`);
|
|
1338
|
+
return lines.join("\r\n");
|
|
1339
|
+
}
|
|
1252
1340
|
function encodeQuotedPrintable(text) {
|
|
1253
1341
|
const utf8Bytes = new TextEncoder().encode(text);
|
|
1254
1342
|
let result = "";
|
|
@@ -1379,12 +1467,13 @@ var SmtpTransport = class {
|
|
|
1379
1467
|
options?.signal?.throwIfAborted();
|
|
1380
1468
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1381
1469
|
options?.signal?.throwIfAborted();
|
|
1382
|
-
const
|
|
1470
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1383
1471
|
await this.returnConnection(connection);
|
|
1384
1472
|
return {
|
|
1385
1473
|
successful: true,
|
|
1386
|
-
messageId,
|
|
1387
|
-
provider: "smtp"
|
|
1474
|
+
messageId: result.messageId,
|
|
1475
|
+
provider: "smtp",
|
|
1476
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1388
1477
|
};
|
|
1389
1478
|
} catch (error) {
|
|
1390
1479
|
if (connection != null) await this.discardConnection(connection);
|
|
@@ -1448,11 +1537,12 @@ var SmtpTransport = class {
|
|
|
1448
1537
|
try {
|
|
1449
1538
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1450
1539
|
options?.signal?.throwIfAborted();
|
|
1451
|
-
const
|
|
1540
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1452
1541
|
yield {
|
|
1453
1542
|
successful: true,
|
|
1454
|
-
messageId,
|
|
1455
|
-
provider: "smtp"
|
|
1543
|
+
messageId: result.messageId,
|
|
1544
|
+
provider: "smtp",
|
|
1545
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1456
1546
|
};
|
|
1457
1547
|
} catch (error) {
|
|
1458
1548
|
options?.signal?.throwIfAborted();
|
|
@@ -1469,11 +1559,12 @@ var SmtpTransport = class {
|
|
|
1469
1559
|
try {
|
|
1470
1560
|
const smtpMessage = await convertMessage(message, this.config.dkim);
|
|
1471
1561
|
options?.signal?.throwIfAborted();
|
|
1472
|
-
const
|
|
1562
|
+
const result = await connection.sendMessage(smtpMessage, options?.signal);
|
|
1473
1563
|
yield {
|
|
1474
1564
|
successful: true,
|
|
1475
|
-
messageId,
|
|
1476
|
-
provider: "smtp"
|
|
1565
|
+
messageId: result.messageId,
|
|
1566
|
+
provider: "smtp",
|
|
1567
|
+
rejectedRecipients: result.rejectedRecipients
|
|
1477
1568
|
};
|
|
1478
1569
|
} catch (error) {
|
|
1479
1570
|
options?.signal?.throwIfAborted();
|
|
@@ -1582,7 +1673,8 @@ function createSmtpFailure(message, error) {
|
|
|
1582
1673
|
attempts: 1,
|
|
1583
1674
|
providerDetails: {
|
|
1584
1675
|
command: error.command,
|
|
1585
|
-
response: error.response
|
|
1676
|
+
response: error.response,
|
|
1677
|
+
rejectedRecipients: error.rejectedRecipients
|
|
1586
1678
|
}
|
|
1587
1679
|
});
|
|
1588
1680
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@upyo/smtp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
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.5.
|
|
56
|
+
"@upyo/core": "0.5.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"tsdown": "^0.12.7",
|