@upyo/smtp 0.6.0-dev.305 → 0.6.0-dev.306

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 CHANGED
@@ -219,13 +219,50 @@ structure.
219
219
  [RFC 3463]: https://www.rfc-editor.org/rfc/rfc3463
220
220
 
221
221
 
222
+ Envelope overrides
223
+ ------------------
224
+
225
+ Use the `envelope` send option when the SMTP reverse-path or recipients must
226
+ differ from the visible message headers:
227
+
228
+ ~~~~ typescript
229
+ const receipt = await transport.send(message, {
230
+ envelope: {
231
+ from: "bounces+customer-42@bounce.example.com",
232
+ to: ["delivery@example.net"],
233
+ },
234
+ });
235
+ ~~~~
236
+
237
+ Omitting `from` or `to` keeps the value derived from the message. Set `from`
238
+ to `null` to send `MAIL FROM:<>`. The override changes only `MAIL FROM` and
239
+ `RCPT TO`; the From, To, Cc, Bcc, and Reply-To message fields remain unchanged.
240
+
241
+ For bulk VERP delivery, pass a resolver that returns an override for each
242
+ message:
243
+
244
+ ~~~~ typescript
245
+ for await (const receipt of transport.sendMany(messages, {
246
+ envelope: (_message, index) => ({
247
+ from: `bounces+${index}@bounce.example.com`,
248
+ }),
249
+ })) {
250
+ console.log(receipt);
251
+ }
252
+ ~~~~
253
+
254
+ A plain override applies to every message passed to `sendMany()`. Invalid
255
+ addresses and empty recipient lists return a non-retryable failed receipt with
256
+ the code `smtp.envelope-invalid` before `MAIL FROM` is sent.
257
+
258
+
222
259
  Internationalized addresses
223
260
  ---------------------------
224
261
 
225
- The transport automatically negotiates [RFC 6531] SMTPUTF8 when any sender,
226
- recipient, or reply-to mailbox contains a non-ASCII character. A supporting
227
- server must advertise both `SMTPUTF8` and `8BITMIME`; Upyo then sends
228
- `BODY=8BITMIME SMTPUTF8` on `MAIL FROM`.
262
+ The transport automatically negotiates [RFC 6531] SMTPUTF8 when the effective
263
+ SMTP envelope or a visible message-header mailbox contains a non-ASCII
264
+ character. A supporting server must advertise both `SMTPUTF8` and `8BITMIME`;
265
+ Upyo then sends `BODY=8BITMIME SMTPUTF8` on `MAIL FROM`.
229
266
 
230
267
  If either capability is missing, delivery returns a non-retryable failed
231
268
  receipt with the code `smtp.smtputf8-unsupported` before the mail transaction
@@ -258,9 +295,10 @@ const receipt = await transport.send(message, {
258
295
 
259
296
  `envelopeId` and `return` become `ENVID` and `RET` parameters on `MAIL FROM`.
260
297
  Each recipient's `notify` and `originalRecipient` values become `NOTIFY` and
261
- `ORCPT` parameters on its `RCPT TO` command. Upyo validates and `xtext`-escapes
262
- the values without adding them to the message headers. `envelopeId` must not
263
- be empty, and `originalRecipient` must exactly match its envelope recipient.
298
+ `ORCPT` parameters on its `RCPT TO` command. Upyo validates them against the
299
+ effective envelope, including any override, and `xtext`-escapes the values
300
+ without adding them to the message headers. `envelopeId` must not be empty,
301
+ and `originalRecipient` must exactly match its envelope recipient.
264
302
 
265
303
  If the server does not advertise `DSN`, the transport returns a non-retryable
266
304
  failed receipt with the code `smtp.dsn-unsupported` before sending `MAIL FROM`.
package/dist/index.cjs CHANGED
@@ -65,14 +65,14 @@ const NOTIFICATION_CONDITIONS = new Set([
65
65
  /**
66
66
  * Validates and serializes the RFC 3461 envelope parameters for a message.
67
67
  *
68
- * @param message The message whose SMTP envelope will carry the parameters.
68
+ * @param envelope The effective SMTP envelope that will carry the parameters.
69
69
  * @param dsn The caller-supplied delivery status notification settings.
70
70
  * @returns Serialized parameters, or `undefined` when none were requested.
71
71
  * @throws {SmtpDsnValidationError} If any setting violates RFC 3461 or does
72
72
  * not correspond to the message envelope.
73
73
  * @internal
74
74
  */
75
- function resolveSmtpDsn(message, dsn) {
75
+ function resolveSmtpDsn(envelope, dsn) {
76
76
  if (dsn == null) return void 0;
77
77
  if (typeof dsn !== "object" || Array.isArray(dsn)) throw new SmtpDsnValidationError("DSN options must be an object.");
78
78
  const mailParameters = [];
@@ -87,11 +87,7 @@ function resolveSmtpDsn(message, dsn) {
87
87
  assertParameterLength(parameter, 100, "ENVID");
88
88
  mailParameters.push(parameter);
89
89
  }
90
- const envelopeRecipients = [
91
- ...message.recipients.map((recipient) => recipient.address),
92
- ...message.ccRecipients.map((recipient) => recipient.address),
93
- ...message.bccRecipients.map((recipient) => recipient.address)
94
- ];
90
+ const envelopeRecipients = envelope.to;
95
91
  const envelopeRecipientSet = new Set(envelopeRecipients);
96
92
  const configuredRecipients = dsn.recipients;
97
93
  if (configuredRecipients != null && (typeof configuredRecipients !== "object" || Array.isArray(configuredRecipients))) throw new SmtpDsnValidationError("DSN recipient options must be an object.");
@@ -1075,7 +1071,7 @@ var SmtpConnection = class {
1075
1071
  const dsn = message.envelope.dsn;
1076
1072
  if (dsn != null && !this.capabilities.some((capability) => /^DSN[ \t]*$/i.test(capability))) throw new SmtpDsnUnsupportedError();
1077
1073
  const mailDsnParameters = dsn == null || dsn.mailParameters.length === 0 ? "" : ` ${dsn.mailParameters.join(" ")}`;
1078
- const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
1074
+ const mailCommand = `MAIL FROM:<${message.envelope.from ?? ""}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
1079
1075
  const recipientCommands = message.envelope.to.map((recipient, index) => {
1080
1076
  const parameters = dsn?.recipientParameters[index] ?? [];
1081
1077
  const suffix = parameters.length === 0 ? "" : ` ${parameters.join(" ")}`;
@@ -1227,6 +1223,52 @@ function decodeOAuth2Challenge(message) {
1227
1223
  }
1228
1224
  }
1229
1225
 
1226
+ //#endregion
1227
+ //#region src/envelope.ts
1228
+ /**
1229
+ * Error produced when an effective SMTP envelope is invalid.
1230
+ *
1231
+ * @since 0.6.0
1232
+ */
1233
+ var SmtpEnvelopeValidationError = class extends TypeError {
1234
+ /**
1235
+ * Creates an SMTP envelope validation error.
1236
+ *
1237
+ * @param message A description of the invalid envelope.
1238
+ */
1239
+ constructor(message) {
1240
+ super(message);
1241
+ this.name = "SmtpEnvelopeValidationError";
1242
+ }
1243
+ };
1244
+ /**
1245
+ * Resolves and validates the SMTP envelope for a message.
1246
+ *
1247
+ * @param message The message that supplies fields without an override.
1248
+ * @param override Optional sender and recipient overrides.
1249
+ * @returns The validated effective SMTP envelope.
1250
+ * @throws {SmtpEnvelopeValidationError} If the override or any effective
1251
+ * address is invalid, or if the effective recipient list is empty.
1252
+ * @internal
1253
+ */
1254
+ function resolveSmtpEnvelope(message, override) {
1255
+ if (override !== void 0 && (override === null || typeof override !== "object" || Array.isArray(override))) throw new SmtpEnvelopeValidationError("SMTP envelope options must be an object.");
1256
+ const from = override?.from === void 0 ? message.sender.address : override.from;
1257
+ if (from !== null && !(0, __upyo_core.isEmailAddress)(from)) throw new SmtpEnvelopeValidationError("SMTP envelope sender must be a valid email address or null.");
1258
+ const recipients = override?.to === void 0 ? [
1259
+ ...message.recipients.map((recipient) => recipient.address),
1260
+ ...message.ccRecipients.map((recipient) => recipient.address),
1261
+ ...message.bccRecipients.map((recipient) => recipient.address)
1262
+ ] : override.to;
1263
+ if (!Array.isArray(recipients)) throw new SmtpEnvelopeValidationError("SMTP envelope recipients must be an array.");
1264
+ if (recipients.length === 0) throw new SmtpEnvelopeValidationError("SMTP envelope must contain at least one recipient.");
1265
+ for (const [index, recipient] of recipients.entries()) if (!(0, __upyo_core.isEmailAddress)(recipient)) throw new SmtpEnvelopeValidationError(`SMTP envelope recipient at index ${index} must be a valid email address.`);
1266
+ return {
1267
+ from,
1268
+ to: [...recipients]
1269
+ };
1270
+ }
1271
+
1230
1272
  //#endregion
1231
1273
  //#region src/dkim/canonicalize.ts
1232
1274
  /**
@@ -1512,27 +1554,24 @@ function arrayBufferToBase64(buffer) {
1512
1554
  * @param message The message to convert.
1513
1555
  * @param dkimConfig Optional DKIM signing configuration.
1514
1556
  * @param dsn Optional validated SMTP delivery status notification parameters.
1557
+ * @param resolvedEnvelope The validated effective SMTP envelope.
1515
1558
  * @returns The converted SMTP message.
1516
1559
  * @throws {RangeError} If a header contains a token that cannot be folded
1517
1560
  * within the RFC 5322 hard line-length limit.
1518
1561
  */
1519
- async function convertMessage(message, dkimConfig, dsn) {
1562
+ async function convertMessage(message, dkimConfig, dsn, resolvedEnvelope = resolveSmtpEnvelope(message)) {
1520
1563
  const envelope = {
1521
- from: message.sender.address,
1522
- to: [
1523
- ...message.recipients.map((r) => r.address),
1524
- ...message.ccRecipients.map((r) => r.address),
1525
- ...message.bccRecipients.map((r) => r.address)
1526
- ],
1564
+ ...resolvedEnvelope,
1527
1565
  dsn
1528
1566
  };
1529
- const requiresSmtpUtf8 = [
1530
- message.sender,
1531
- ...message.recipients,
1532
- ...message.ccRecipients,
1533
- ...message.bccRecipients,
1534
- ...message.replyRecipients
1535
- ].some((address) => Array.from(address.address).some((character) => (character.codePointAt(0) ?? 0) > 127));
1567
+ const headerAddresses = [
1568
+ message.sender.address,
1569
+ ...message.recipients.map((address) => address.address),
1570
+ ...message.ccRecipients.map((address) => address.address),
1571
+ ...message.replyRecipients.map((address) => address.address)
1572
+ ];
1573
+ const envelopeAddresses = [...envelope.from == null ? [] : [envelope.from], ...envelope.to];
1574
+ const requiresSmtpUtf8 = [...headerAddresses, ...envelopeAddresses].some((address) => Array.from(address).some((character) => (character.codePointAt(0) ?? 0) > 127));
1536
1575
  let raw = await buildRawMessage(message);
1537
1576
  if (dkimConfig) try {
1538
1577
  for (const sig of dkimConfig.signatures) {
@@ -1835,8 +1874,8 @@ var SmtpTransport = class {
1835
1874
  * ```
1836
1875
  *
1837
1876
  * @param message The email message to send.
1838
- * @param options Optional transport options including `AbortSignal` for
1839
- * cancellation.
1877
+ * @param options Optional SMTP envelope, delivery status, and cancellation
1878
+ * settings.
1840
1879
  * @returns A promise that resolves to a receipt indicating success or
1841
1880
  * failure.
1842
1881
  * @throws {DOMException} If the operation is aborted through
@@ -1846,10 +1885,11 @@ var SmtpTransport = class {
1846
1885
  options?.signal?.throwIfAborted();
1847
1886
  let connection;
1848
1887
  try {
1849
- const dsn = resolveSmtpDsn(message, options?.dsn);
1888
+ const envelope = resolveEnvelopeOption(message, options?.envelope, 0);
1889
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1850
1890
  connection = await this.getConnection(options?.signal);
1851
1891
  options?.signal?.throwIfAborted();
1852
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1892
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1853
1893
  options?.signal?.throwIfAborted();
1854
1894
  const result = await connection.sendMessage(smtpMessage, options?.signal);
1855
1895
  await this.returnConnection(connection);
@@ -1890,8 +1930,8 @@ var SmtpTransport = class {
1890
1930
  * ```
1891
1931
  *
1892
1932
  * @param messages An iterable or async iterable of messages to send.
1893
- * @param options Optional transport options including `AbortSignal` for
1894
- * cancellation.
1933
+ * @param options Optional SMTP envelope, delivery status, and cancellation
1934
+ * settings.
1895
1935
  * @returns An async iterable of receipts, one for each message.
1896
1936
  * @throws {DOMException} If the operation is aborted through
1897
1937
  * `options.signal`.
@@ -1913,50 +1953,57 @@ var SmtpTransport = class {
1913
1953
  let connectionValid = true;
1914
1954
  try {
1915
1955
  const isAsyncIterable = Symbol.asyncIterator in messages;
1916
- if (isAsyncIterable) for await (const message of messages) {
1917
- options?.signal?.throwIfAborted();
1918
- if (!connectionValid) {
1919
- yield createSmtpFailure("Connection is no longer valid");
1920
- continue;
1921
- }
1922
- try {
1923
- const dsn = resolveSmtpDsn(message, options?.dsn);
1924
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1956
+ if (isAsyncIterable) {
1957
+ let index = 0;
1958
+ for await (const message of messages) {
1925
1959
  options?.signal?.throwIfAborted();
1926
- const result = await connection.sendMessage(smtpMessage, options?.signal);
1927
- yield {
1928
- successful: true,
1929
- messageId: result.messageId,
1930
- provider: "smtp",
1931
- rejectedRecipients: result.rejectedRecipients
1932
- };
1933
- } catch (error) {
1934
- options?.signal?.throwIfAborted();
1935
- if (!isReusableLocalFailure(error)) connectionValid = false;
1936
- yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1937
- }
1938
- }
1939
- else for (const message of messages) {
1940
- options?.signal?.throwIfAborted();
1941
- if (!connectionValid) {
1942
- yield createSmtpFailure("Connection is no longer valid");
1943
- continue;
1960
+ if (!connectionValid) {
1961
+ yield createSmtpFailure("Connection is no longer valid");
1962
+ continue;
1963
+ }
1964
+ try {
1965
+ const envelope = resolveEnvelopeOption(message, options?.envelope, index++);
1966
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1967
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1968
+ options?.signal?.throwIfAborted();
1969
+ const result = await connection.sendMessage(smtpMessage, options?.signal);
1970
+ yield {
1971
+ successful: true,
1972
+ messageId: result.messageId,
1973
+ provider: "smtp",
1974
+ rejectedRecipients: result.rejectedRecipients
1975
+ };
1976
+ } catch (error) {
1977
+ options?.signal?.throwIfAborted();
1978
+ if (!isReusableLocalFailure(error)) connectionValid = false;
1979
+ yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1980
+ }
1944
1981
  }
1945
- try {
1946
- const dsn = resolveSmtpDsn(message, options?.dsn);
1947
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1982
+ } else {
1983
+ let index = 0;
1984
+ for (const message of messages) {
1948
1985
  options?.signal?.throwIfAborted();
1949
- const result = await connection.sendMessage(smtpMessage, options?.signal);
1950
- yield {
1951
- successful: true,
1952
- messageId: result.messageId,
1953
- provider: "smtp",
1954
- rejectedRecipients: result.rejectedRecipients
1955
- };
1956
- } catch (error) {
1957
- options?.signal?.throwIfAborted();
1958
- if (!isReusableLocalFailure(error)) connectionValid = false;
1959
- yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1986
+ if (!connectionValid) {
1987
+ yield createSmtpFailure("Connection is no longer valid");
1988
+ continue;
1989
+ }
1990
+ try {
1991
+ const envelope = resolveEnvelopeOption(message, options?.envelope, index++);
1992
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1993
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1994
+ options?.signal?.throwIfAborted();
1995
+ const result = await connection.sendMessage(smtpMessage, options?.signal);
1996
+ yield {
1997
+ successful: true,
1998
+ messageId: result.messageId,
1999
+ provider: "smtp",
2000
+ rejectedRecipients: result.rejectedRecipients
2001
+ };
2002
+ } catch (error) {
2003
+ options?.signal?.throwIfAborted();
2004
+ if (!isReusableLocalFailure(error)) connectionValid = false;
2005
+ yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
2006
+ }
1960
2007
  }
1961
2008
  }
1962
2009
  if (connectionValid) await this.returnConnection(connection);
@@ -2050,6 +2097,13 @@ var SmtpTransport = class {
2050
2097
  }
2051
2098
  };
2052
2099
  function createSmtpFailure(message, error) {
2100
+ if (error instanceof SmtpEnvelopeValidationError) return (0, __upyo_core.createFailedReceipt)(message, {
2101
+ provider: "smtp",
2102
+ code: "smtp.envelope-invalid",
2103
+ category: "validation",
2104
+ retryable: false,
2105
+ attempts: 1
2106
+ });
2053
2107
  if (error instanceof SmtpDsnValidationError) return (0, __upyo_core.createFailedReceipt)(message, {
2054
2108
  provider: "smtp",
2055
2109
  code: "smtp.dsn-invalid",
@@ -2106,7 +2160,19 @@ function createSmtpFailure(message, error) {
2106
2160
  });
2107
2161
  }
2108
2162
  function isReusableLocalFailure(error) {
2109
- return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2163
+ return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpEnvelopeValidationError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2164
+ }
2165
+ function resolveEnvelopeOption(message, option, index) {
2166
+ let override;
2167
+ if (typeof option === "function") try {
2168
+ override = option(message, index);
2169
+ } catch (error) {
2170
+ const failure = new SmtpEnvelopeValidationError(`SMTP envelope resolver failed: ${error instanceof Error ? error.message : String(error)}`);
2171
+ failure.cause = error;
2172
+ throw failure;
2173
+ }
2174
+ else override = option;
2175
+ return resolveSmtpEnvelope(message, override);
2110
2176
  }
2111
2177
  function classifySmtpReply(code, enhancedStatusCode) {
2112
2178
  const retryable = enhancedStatusCode == null ? code >= 400 && code < 500 : enhancedStatusCode.class === 4;
@@ -2149,5 +2215,6 @@ function isSmtpResponseProviderDetails(value) {
2149
2215
  exports.SmtpAuthError = SmtpAuthError;
2150
2216
  exports.SmtpDsnUnsupportedError = SmtpDsnUnsupportedError;
2151
2217
  exports.SmtpDsnValidationError = SmtpDsnValidationError;
2218
+ exports.SmtpEnvelopeValidationError = SmtpEnvelopeValidationError;
2152
2219
  exports.SmtpTransport = SmtpTransport;
2153
2220
  exports.isSmtpResponseProviderDetails = isSmtpResponseProviderDetails;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { Message, Receipt, Transport, TransportOptions } from "@upyo/core";
1
+ import { EmailAddress, Message, Receipt, Transport, TransportOptions } from "@upyo/core";
2
2
 
3
3
  //#region src/dkim/types.d.ts
4
4
 
@@ -417,6 +417,54 @@ interface SmtpTlsOptions {
417
417
  * used internally by the SMTP transport implementation.
418
418
  */
419
419
  //#endregion
420
+ //#region src/envelope.d.ts
421
+ /**
422
+ * Overrides for the SMTP envelope of one message.
423
+ *
424
+ * Omitted fields are derived from the corresponding message fields. A `null`
425
+ * sender produces the null reverse-path used for delivery notifications.
426
+ *
427
+ * @since 0.6.0
428
+ */
429
+ interface SmtpEnvelopeOptions {
430
+ /**
431
+ * The address for `MAIL FROM`, or `null` for `MAIL FROM:<>`.
432
+ *
433
+ * When omitted, the message sender is used.
434
+ */
435
+ readonly from?: EmailAddress | null;
436
+ /**
437
+ * The addresses for `RCPT TO`.
438
+ *
439
+ * When omitted, the message's To, Cc, and Bcc addresses are used.
440
+ */
441
+ readonly to?: readonly EmailAddress[];
442
+ }
443
+ /**
444
+ * Resolves an SMTP envelope override for one message in a batch.
445
+ *
446
+ * @param message The message whose envelope is being resolved.
447
+ * @param index The zero-based position of the message in the send operation.
448
+ * @returns The envelope override, or `undefined` to derive the envelope from
449
+ * the message.
450
+ * @since 0.6.0
451
+ */
452
+ type SmtpEnvelopeResolver = (message: Message, index: number) => SmtpEnvelopeOptions | undefined;
453
+ /**
454
+ * Error produced when an effective SMTP envelope is invalid.
455
+ *
456
+ * @since 0.6.0
457
+ */
458
+ declare class SmtpEnvelopeValidationError extends TypeError {
459
+ /**
460
+ * Creates an SMTP envelope validation error.
461
+ *
462
+ * @param message A description of the invalid envelope.
463
+ */
464
+ constructor(message: string);
465
+ }
466
+ /** @internal */
467
+ //#endregion
420
468
  //#region src/delivery-status.d.ts
421
469
  /**
422
470
  * A condition under which an SMTP server should issue a delivery status
@@ -481,6 +529,13 @@ interface SmtpDsnOptions {
481
529
  * @since 0.6.0
482
530
  */
483
531
  interface SmtpTransportOptions extends TransportOptions {
532
+ /**
533
+ * SMTP envelope overrides for this send operation.
534
+ *
535
+ * A resolver can return a different envelope for each message passed to
536
+ * {@link SmtpTransport.sendMany}.
537
+ */
538
+ readonly envelope?: SmtpEnvelopeOptions | SmtpEnvelopeResolver;
484
539
  /** Delivery status notification settings for this SMTP transaction. */
485
540
  readonly dsn?: SmtpDsnOptions;
486
541
  }
@@ -694,8 +749,8 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
694
749
  * ```
695
750
  *
696
751
  * @param message The email message to send.
697
- * @param options Optional transport options including `AbortSignal` for
698
- * cancellation.
752
+ * @param options Optional SMTP envelope, delivery status, and cancellation
753
+ * settings.
699
754
  * @returns A promise that resolves to a receipt indicating success or
700
755
  * failure.
701
756
  * @throws {DOMException} If the operation is aborted through
@@ -726,8 +781,8 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
726
781
  * ```
727
782
  *
728
783
  * @param messages An iterable or async iterable of messages to send.
729
- * @param options Optional transport options including `AbortSignal` for
730
- * cancellation.
784
+ * @param options Optional SMTP envelope, delivery status, and cancellation
785
+ * settings.
731
786
  * @returns An async iterable of receipts, one for each message.
732
787
  * @throws {DOMException} If the operation is aborted through
733
788
  * `options.signal`.
@@ -797,4 +852,4 @@ declare class SmtpAuthError extends Error {
797
852
  */
798
853
 
799
854
  //#endregion
800
- export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpEnhancedStatusCode, SmtpMessageSizeProviderDetails, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpProviderDetails, SmtpReceipt, SmtpRejectedRecipient, SmtpResponseProviderDetails, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth, SmtpUtf8ProviderDetails, isSmtpResponseProviderDetails };
855
+ export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpEnhancedStatusCode, SmtpEnvelopeOptions, SmtpEnvelopeResolver, SmtpEnvelopeValidationError, SmtpMessageSizeProviderDetails, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpProviderDetails, SmtpReceipt, SmtpRejectedRecipient, SmtpResponseProviderDetails, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth, SmtpUtf8ProviderDetails, isSmtpResponseProviderDetails };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Message, Receipt, Transport, TransportOptions } from "@upyo/core";
1
+ import { EmailAddress, Message, Receipt, Transport, TransportOptions } from "@upyo/core";
2
2
 
3
3
  //#region src/dkim/types.d.ts
4
4
 
@@ -417,6 +417,54 @@ interface SmtpTlsOptions {
417
417
  * used internally by the SMTP transport implementation.
418
418
  */
419
419
  //#endregion
420
+ //#region src/envelope.d.ts
421
+ /**
422
+ * Overrides for the SMTP envelope of one message.
423
+ *
424
+ * Omitted fields are derived from the corresponding message fields. A `null`
425
+ * sender produces the null reverse-path used for delivery notifications.
426
+ *
427
+ * @since 0.6.0
428
+ */
429
+ interface SmtpEnvelopeOptions {
430
+ /**
431
+ * The address for `MAIL FROM`, or `null` for `MAIL FROM:<>`.
432
+ *
433
+ * When omitted, the message sender is used.
434
+ */
435
+ readonly from?: EmailAddress | null;
436
+ /**
437
+ * The addresses for `RCPT TO`.
438
+ *
439
+ * When omitted, the message's To, Cc, and Bcc addresses are used.
440
+ */
441
+ readonly to?: readonly EmailAddress[];
442
+ }
443
+ /**
444
+ * Resolves an SMTP envelope override for one message in a batch.
445
+ *
446
+ * @param message The message whose envelope is being resolved.
447
+ * @param index The zero-based position of the message in the send operation.
448
+ * @returns The envelope override, or `undefined` to derive the envelope from
449
+ * the message.
450
+ * @since 0.6.0
451
+ */
452
+ type SmtpEnvelopeResolver = (message: Message, index: number) => SmtpEnvelopeOptions | undefined;
453
+ /**
454
+ * Error produced when an effective SMTP envelope is invalid.
455
+ *
456
+ * @since 0.6.0
457
+ */
458
+ declare class SmtpEnvelopeValidationError extends TypeError {
459
+ /**
460
+ * Creates an SMTP envelope validation error.
461
+ *
462
+ * @param message A description of the invalid envelope.
463
+ */
464
+ constructor(message: string);
465
+ }
466
+ /** @internal */
467
+ //#endregion
420
468
  //#region src/delivery-status.d.ts
421
469
  /**
422
470
  * A condition under which an SMTP server should issue a delivery status
@@ -481,6 +529,13 @@ interface SmtpDsnOptions {
481
529
  * @since 0.6.0
482
530
  */
483
531
  interface SmtpTransportOptions extends TransportOptions {
532
+ /**
533
+ * SMTP envelope overrides for this send operation.
534
+ *
535
+ * A resolver can return a different envelope for each message passed to
536
+ * {@link SmtpTransport.sendMany}.
537
+ */
538
+ readonly envelope?: SmtpEnvelopeOptions | SmtpEnvelopeResolver;
484
539
  /** Delivery status notification settings for this SMTP transaction. */
485
540
  readonly dsn?: SmtpDsnOptions;
486
541
  }
@@ -694,8 +749,8 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
694
749
  * ```
695
750
  *
696
751
  * @param message The email message to send.
697
- * @param options Optional transport options including `AbortSignal` for
698
- * cancellation.
752
+ * @param options Optional SMTP envelope, delivery status, and cancellation
753
+ * settings.
699
754
  * @returns A promise that resolves to a receipt indicating success or
700
755
  * failure.
701
756
  * @throws {DOMException} If the operation is aborted through
@@ -726,8 +781,8 @@ declare class SmtpTransport implements Transport<"smtp">, AsyncDisposable {
726
781
  * ```
727
782
  *
728
783
  * @param messages An iterable or async iterable of messages to send.
729
- * @param options Optional transport options including `AbortSignal` for
730
- * cancellation.
784
+ * @param options Optional SMTP envelope, delivery status, and cancellation
785
+ * settings.
731
786
  * @returns An async iterable of receipts, one for each message.
732
787
  * @throws {DOMException} If the operation is aborted through
733
788
  * `options.signal`.
@@ -797,4 +852,4 @@ declare class SmtpAuthError extends Error {
797
852
  */
798
853
 
799
854
  //#endregion
800
- export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpEnhancedStatusCode, SmtpMessageSizeProviderDetails, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpProviderDetails, SmtpReceipt, SmtpRejectedRecipient, SmtpResponseProviderDetails, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth, SmtpUtf8ProviderDetails, isSmtpResponseProviderDetails };
855
+ export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpEnhancedStatusCode, SmtpEnvelopeOptions, SmtpEnvelopeResolver, SmtpEnvelopeValidationError, SmtpMessageSizeProviderDetails, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpProviderDetails, SmtpReceipt, SmtpRejectedRecipient, SmtpResponseProviderDetails, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth, SmtpUtf8ProviderDetails, isSmtpResponseProviderDetails };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createFailedReceipt } from "@upyo/core";
1
+ import { createFailedReceipt, isEmailAddress } from "@upyo/core";
2
2
  import { Buffer } from "node:buffer";
3
3
  import { Socket } from "node:net";
4
4
  import { TLSSocket, connect } from "node:tls";
@@ -42,14 +42,14 @@ const NOTIFICATION_CONDITIONS = new Set([
42
42
  /**
43
43
  * Validates and serializes the RFC 3461 envelope parameters for a message.
44
44
  *
45
- * @param message The message whose SMTP envelope will carry the parameters.
45
+ * @param envelope The effective SMTP envelope that will carry the parameters.
46
46
  * @param dsn The caller-supplied delivery status notification settings.
47
47
  * @returns Serialized parameters, or `undefined` when none were requested.
48
48
  * @throws {SmtpDsnValidationError} If any setting violates RFC 3461 or does
49
49
  * not correspond to the message envelope.
50
50
  * @internal
51
51
  */
52
- function resolveSmtpDsn(message, dsn) {
52
+ function resolveSmtpDsn(envelope, dsn) {
53
53
  if (dsn == null) return void 0;
54
54
  if (typeof dsn !== "object" || Array.isArray(dsn)) throw new SmtpDsnValidationError("DSN options must be an object.");
55
55
  const mailParameters = [];
@@ -64,11 +64,7 @@ function resolveSmtpDsn(message, dsn) {
64
64
  assertParameterLength(parameter, 100, "ENVID");
65
65
  mailParameters.push(parameter);
66
66
  }
67
- const envelopeRecipients = [
68
- ...message.recipients.map((recipient) => recipient.address),
69
- ...message.ccRecipients.map((recipient) => recipient.address),
70
- ...message.bccRecipients.map((recipient) => recipient.address)
71
- ];
67
+ const envelopeRecipients = envelope.to;
72
68
  const envelopeRecipientSet = new Set(envelopeRecipients);
73
69
  const configuredRecipients = dsn.recipients;
74
70
  if (configuredRecipients != null && (typeof configuredRecipients !== "object" || Array.isArray(configuredRecipients))) throw new SmtpDsnValidationError("DSN recipient options must be an object.");
@@ -1052,7 +1048,7 @@ var SmtpConnection = class {
1052
1048
  const dsn = message.envelope.dsn;
1053
1049
  if (dsn != null && !this.capabilities.some((capability) => /^DSN[ \t]*$/i.test(capability))) throw new SmtpDsnUnsupportedError();
1054
1050
  const mailDsnParameters = dsn == null || dsn.mailParameters.length === 0 ? "" : ` ${dsn.mailParameters.join(" ")}`;
1055
- const mailCommand = `MAIL FROM:<${message.envelope.from}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
1051
+ const mailCommand = `MAIL FROM:<${message.envelope.from ?? ""}>${sizeParameter}${smtpUtf8Parameters}${mailDsnParameters}`;
1056
1052
  const recipientCommands = message.envelope.to.map((recipient, index) => {
1057
1053
  const parameters = dsn?.recipientParameters[index] ?? [];
1058
1054
  const suffix = parameters.length === 0 ? "" : ` ${parameters.join(" ")}`;
@@ -1204,6 +1200,52 @@ function decodeOAuth2Challenge(message) {
1204
1200
  }
1205
1201
  }
1206
1202
 
1203
+ //#endregion
1204
+ //#region src/envelope.ts
1205
+ /**
1206
+ * Error produced when an effective SMTP envelope is invalid.
1207
+ *
1208
+ * @since 0.6.0
1209
+ */
1210
+ var SmtpEnvelopeValidationError = class extends TypeError {
1211
+ /**
1212
+ * Creates an SMTP envelope validation error.
1213
+ *
1214
+ * @param message A description of the invalid envelope.
1215
+ */
1216
+ constructor(message) {
1217
+ super(message);
1218
+ this.name = "SmtpEnvelopeValidationError";
1219
+ }
1220
+ };
1221
+ /**
1222
+ * Resolves and validates the SMTP envelope for a message.
1223
+ *
1224
+ * @param message The message that supplies fields without an override.
1225
+ * @param override Optional sender and recipient overrides.
1226
+ * @returns The validated effective SMTP envelope.
1227
+ * @throws {SmtpEnvelopeValidationError} If the override or any effective
1228
+ * address is invalid, or if the effective recipient list is empty.
1229
+ * @internal
1230
+ */
1231
+ function resolveSmtpEnvelope(message, override) {
1232
+ if (override !== void 0 && (override === null || typeof override !== "object" || Array.isArray(override))) throw new SmtpEnvelopeValidationError("SMTP envelope options must be an object.");
1233
+ const from = override?.from === void 0 ? message.sender.address : override.from;
1234
+ if (from !== null && !isEmailAddress(from)) throw new SmtpEnvelopeValidationError("SMTP envelope sender must be a valid email address or null.");
1235
+ const recipients = override?.to === void 0 ? [
1236
+ ...message.recipients.map((recipient) => recipient.address),
1237
+ ...message.ccRecipients.map((recipient) => recipient.address),
1238
+ ...message.bccRecipients.map((recipient) => recipient.address)
1239
+ ] : override.to;
1240
+ if (!Array.isArray(recipients)) throw new SmtpEnvelopeValidationError("SMTP envelope recipients must be an array.");
1241
+ if (recipients.length === 0) throw new SmtpEnvelopeValidationError("SMTP envelope must contain at least one recipient.");
1242
+ for (const [index, recipient] of recipients.entries()) if (!isEmailAddress(recipient)) throw new SmtpEnvelopeValidationError(`SMTP envelope recipient at index ${index} must be a valid email address.`);
1243
+ return {
1244
+ from,
1245
+ to: [...recipients]
1246
+ };
1247
+ }
1248
+
1207
1249
  //#endregion
1208
1250
  //#region src/dkim/canonicalize.ts
1209
1251
  /**
@@ -1489,27 +1531,24 @@ function arrayBufferToBase64(buffer) {
1489
1531
  * @param message The message to convert.
1490
1532
  * @param dkimConfig Optional DKIM signing configuration.
1491
1533
  * @param dsn Optional validated SMTP delivery status notification parameters.
1534
+ * @param resolvedEnvelope The validated effective SMTP envelope.
1492
1535
  * @returns The converted SMTP message.
1493
1536
  * @throws {RangeError} If a header contains a token that cannot be folded
1494
1537
  * within the RFC 5322 hard line-length limit.
1495
1538
  */
1496
- async function convertMessage(message, dkimConfig, dsn) {
1539
+ async function convertMessage(message, dkimConfig, dsn, resolvedEnvelope = resolveSmtpEnvelope(message)) {
1497
1540
  const envelope = {
1498
- from: message.sender.address,
1499
- to: [
1500
- ...message.recipients.map((r) => r.address),
1501
- ...message.ccRecipients.map((r) => r.address),
1502
- ...message.bccRecipients.map((r) => r.address)
1503
- ],
1541
+ ...resolvedEnvelope,
1504
1542
  dsn
1505
1543
  };
1506
- const requiresSmtpUtf8 = [
1507
- message.sender,
1508
- ...message.recipients,
1509
- ...message.ccRecipients,
1510
- ...message.bccRecipients,
1511
- ...message.replyRecipients
1512
- ].some((address) => Array.from(address.address).some((character) => (character.codePointAt(0) ?? 0) > 127));
1544
+ const headerAddresses = [
1545
+ message.sender.address,
1546
+ ...message.recipients.map((address) => address.address),
1547
+ ...message.ccRecipients.map((address) => address.address),
1548
+ ...message.replyRecipients.map((address) => address.address)
1549
+ ];
1550
+ const envelopeAddresses = [...envelope.from == null ? [] : [envelope.from], ...envelope.to];
1551
+ const requiresSmtpUtf8 = [...headerAddresses, ...envelopeAddresses].some((address) => Array.from(address).some((character) => (character.codePointAt(0) ?? 0) > 127));
1513
1552
  let raw = await buildRawMessage(message);
1514
1553
  if (dkimConfig) try {
1515
1554
  for (const sig of dkimConfig.signatures) {
@@ -1812,8 +1851,8 @@ var SmtpTransport = class {
1812
1851
  * ```
1813
1852
  *
1814
1853
  * @param message The email message to send.
1815
- * @param options Optional transport options including `AbortSignal` for
1816
- * cancellation.
1854
+ * @param options Optional SMTP envelope, delivery status, and cancellation
1855
+ * settings.
1817
1856
  * @returns A promise that resolves to a receipt indicating success or
1818
1857
  * failure.
1819
1858
  * @throws {DOMException} If the operation is aborted through
@@ -1823,10 +1862,11 @@ var SmtpTransport = class {
1823
1862
  options?.signal?.throwIfAborted();
1824
1863
  let connection;
1825
1864
  try {
1826
- const dsn = resolveSmtpDsn(message, options?.dsn);
1865
+ const envelope = resolveEnvelopeOption(message, options?.envelope, 0);
1866
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1827
1867
  connection = await this.getConnection(options?.signal);
1828
1868
  options?.signal?.throwIfAborted();
1829
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1869
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1830
1870
  options?.signal?.throwIfAborted();
1831
1871
  const result = await connection.sendMessage(smtpMessage, options?.signal);
1832
1872
  await this.returnConnection(connection);
@@ -1867,8 +1907,8 @@ var SmtpTransport = class {
1867
1907
  * ```
1868
1908
  *
1869
1909
  * @param messages An iterable or async iterable of messages to send.
1870
- * @param options Optional transport options including `AbortSignal` for
1871
- * cancellation.
1910
+ * @param options Optional SMTP envelope, delivery status, and cancellation
1911
+ * settings.
1872
1912
  * @returns An async iterable of receipts, one for each message.
1873
1913
  * @throws {DOMException} If the operation is aborted through
1874
1914
  * `options.signal`.
@@ -1890,50 +1930,57 @@ var SmtpTransport = class {
1890
1930
  let connectionValid = true;
1891
1931
  try {
1892
1932
  const isAsyncIterable = Symbol.asyncIterator in messages;
1893
- if (isAsyncIterable) for await (const message of messages) {
1894
- options?.signal?.throwIfAborted();
1895
- if (!connectionValid) {
1896
- yield createSmtpFailure("Connection is no longer valid");
1897
- continue;
1898
- }
1899
- try {
1900
- const dsn = resolveSmtpDsn(message, options?.dsn);
1901
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1933
+ if (isAsyncIterable) {
1934
+ let index = 0;
1935
+ for await (const message of messages) {
1902
1936
  options?.signal?.throwIfAborted();
1903
- const result = await connection.sendMessage(smtpMessage, options?.signal);
1904
- yield {
1905
- successful: true,
1906
- messageId: result.messageId,
1907
- provider: "smtp",
1908
- rejectedRecipients: result.rejectedRecipients
1909
- };
1910
- } catch (error) {
1911
- options?.signal?.throwIfAborted();
1912
- if (!isReusableLocalFailure(error)) connectionValid = false;
1913
- yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1914
- }
1915
- }
1916
- else for (const message of messages) {
1917
- options?.signal?.throwIfAborted();
1918
- if (!connectionValid) {
1919
- yield createSmtpFailure("Connection is no longer valid");
1920
- continue;
1937
+ if (!connectionValid) {
1938
+ yield createSmtpFailure("Connection is no longer valid");
1939
+ continue;
1940
+ }
1941
+ try {
1942
+ const envelope = resolveEnvelopeOption(message, options?.envelope, index++);
1943
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1944
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1945
+ options?.signal?.throwIfAborted();
1946
+ const result = await connection.sendMessage(smtpMessage, options?.signal);
1947
+ yield {
1948
+ successful: true,
1949
+ messageId: result.messageId,
1950
+ provider: "smtp",
1951
+ rejectedRecipients: result.rejectedRecipients
1952
+ };
1953
+ } catch (error) {
1954
+ options?.signal?.throwIfAborted();
1955
+ if (!isReusableLocalFailure(error)) connectionValid = false;
1956
+ yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1957
+ }
1921
1958
  }
1922
- try {
1923
- const dsn = resolveSmtpDsn(message, options?.dsn);
1924
- const smtpMessage = await convertMessage(message, this.config.dkim, dsn);
1959
+ } else {
1960
+ let index = 0;
1961
+ for (const message of messages) {
1925
1962
  options?.signal?.throwIfAborted();
1926
- const result = await connection.sendMessage(smtpMessage, options?.signal);
1927
- yield {
1928
- successful: true,
1929
- messageId: result.messageId,
1930
- provider: "smtp",
1931
- rejectedRecipients: result.rejectedRecipients
1932
- };
1933
- } catch (error) {
1934
- options?.signal?.throwIfAborted();
1935
- if (!isReusableLocalFailure(error)) connectionValid = false;
1936
- yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1963
+ if (!connectionValid) {
1964
+ yield createSmtpFailure("Connection is no longer valid");
1965
+ continue;
1966
+ }
1967
+ try {
1968
+ const envelope = resolveEnvelopeOption(message, options?.envelope, index++);
1969
+ const dsn = resolveSmtpDsn(envelope, options?.dsn);
1970
+ const smtpMessage = await convertMessage(message, this.config.dkim, dsn, envelope);
1971
+ options?.signal?.throwIfAborted();
1972
+ const result = await connection.sendMessage(smtpMessage, options?.signal);
1973
+ yield {
1974
+ successful: true,
1975
+ messageId: result.messageId,
1976
+ provider: "smtp",
1977
+ rejectedRecipients: result.rejectedRecipients
1978
+ };
1979
+ } catch (error) {
1980
+ options?.signal?.throwIfAborted();
1981
+ if (!isReusableLocalFailure(error)) connectionValid = false;
1982
+ yield createSmtpFailure(error instanceof Error ? error.message : String(error), error);
1983
+ }
1937
1984
  }
1938
1985
  }
1939
1986
  if (connectionValid) await this.returnConnection(connection);
@@ -2027,6 +2074,13 @@ var SmtpTransport = class {
2027
2074
  }
2028
2075
  };
2029
2076
  function createSmtpFailure(message, error) {
2077
+ if (error instanceof SmtpEnvelopeValidationError) return createFailedReceipt(message, {
2078
+ provider: "smtp",
2079
+ code: "smtp.envelope-invalid",
2080
+ category: "validation",
2081
+ retryable: false,
2082
+ attempts: 1
2083
+ });
2030
2084
  if (error instanceof SmtpDsnValidationError) return createFailedReceipt(message, {
2031
2085
  provider: "smtp",
2032
2086
  code: "smtp.dsn-invalid",
@@ -2083,7 +2137,19 @@ function createSmtpFailure(message, error) {
2083
2137
  });
2084
2138
  }
2085
2139
  function isReusableLocalFailure(error) {
2086
- return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2140
+ return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpEnvelopeValidationError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2141
+ }
2142
+ function resolveEnvelopeOption(message, option, index) {
2143
+ let override;
2144
+ if (typeof option === "function") try {
2145
+ override = option(message, index);
2146
+ } catch (error) {
2147
+ const failure = new SmtpEnvelopeValidationError(`SMTP envelope resolver failed: ${error instanceof Error ? error.message : String(error)}`);
2148
+ failure.cause = error;
2149
+ throw failure;
2150
+ }
2151
+ else override = option;
2152
+ return resolveSmtpEnvelope(message, override);
2087
2153
  }
2088
2154
  function classifySmtpReply(code, enhancedStatusCode) {
2089
2155
  const retryable = enhancedStatusCode == null ? code >= 400 && code < 500 : enhancedStatusCode.class === 4;
@@ -2123,4 +2189,4 @@ function isSmtpResponseProviderDetails(value) {
2123
2189
  }
2124
2190
 
2125
2191
  //#endregion
2126
- export { SmtpAuthError, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpTransport, isSmtpResponseProviderDetails };
2192
+ export { SmtpAuthError, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpEnvelopeValidationError, SmtpTransport, isSmtpResponseProviderDetails };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/smtp",
3
- "version": "0.6.0-dev.305",
3
+ "version": "0.6.0-dev.306",
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.305+7db15a6d"
56
+ "@upyo/core": "0.6.0-dev.306+6bd78f36"
57
57
  },
58
58
  "devDependencies": {
59
59
  "tsdown": "^0.12.7",