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