@upyo/smtp 0.6.0-dev.296 → 0.6.0-dev.299

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
@@ -28,6 +28,7 @@ Features
28
28
  - Multiple recipients (To, CC, BCC)
29
29
  - SMTP PIPELINING for faster multi-recipient delivery
30
30
  - SMTP SIZE declaration and advertised-limit checks
31
+ - Structured SMTP enhanced status codes
31
32
  - SMTPUTF8 internationalized address delivery
32
33
  - SMTP delivery status notification requests
33
34
  - Custom headers
@@ -184,6 +185,40 @@ See [RFC 1870] for the SMTP Message Size Declaration extension.
184
185
  [RFC 1870]: https://www.rfc-editor.org/rfc/rfc1870
185
186
 
186
187
 
188
+ Enhanced status codes
189
+ ---------------------
190
+
191
+ SMTP failures include a parsed enhanced status code when the server prefixes
192
+ its reply text with a valid RFC 2034 code such as `5.1.1`. The final reply
193
+ line's text remains in `providerDetails.response`, while
194
+ `providerDetails.enhancedStatusCode` provides the complete code and its numeric
195
+ `class`, `subject`, and `detail` fields. Address and message-content statuses
196
+ are categorized as validation failures, and network/routing statuses as network
197
+ failures.
198
+
199
+ Use `isSmtpResponseProviderDetails()` to narrow the provider-specific details:
200
+
201
+ ~~~~ typescript
202
+ import { isSmtpResponseProviderDetails } from "@upyo/smtp";
203
+
204
+ const error = receipt.successful ? undefined : receipt.errors?.[0];
205
+ if (isSmtpResponseProviderDetails(error?.providerDetails)) {
206
+ console.log(error.providerDetails.enhancedStatusCode?.subject);
207
+ }
208
+ ~~~~
209
+
210
+ Partially rejected recipients expose the same structure through
211
+ `receipt.rejectedRecipients[].enhancedStatusCode`. Replies without an enhanced
212
+ code, with malformed fields, or with a class that conflicts with the
213
+ three-digit SMTP reply continue to use the traditional reply classification.
214
+
215
+ See [RFC 2034] for the SMTP extension and [RFC 3463] for the status-code
216
+ structure.
217
+
218
+ [RFC 2034]: https://www.rfc-editor.org/rfc/rfc2034
219
+ [RFC 3463]: https://www.rfc-editor.org/rfc/rfc3463
220
+
221
+
187
222
  Internationalized addresses
188
223
  ---------------------------
189
224
 
package/dist/index.cjs CHANGED
@@ -472,6 +472,34 @@ var OAuth2TokenManager = class {
472
472
  }
473
473
  };
474
474
 
475
+ //#endregion
476
+ //#region src/smtp-status-code.ts
477
+ /**
478
+ * Parses an enhanced SMTP status code from the beginning of reply text.
479
+ *
480
+ * RFC 2034 requires the enhanced class to agree with the three-digit SMTP
481
+ * reply class. RFC 3463 also prohibits leading zeroes and limits the subject
482
+ * and detail fields to three digits.
483
+ *
484
+ * @param replyCode The three-digit SMTP reply code.
485
+ * @param response The textual part of the SMTP reply.
486
+ * @returns The parsed enhanced status code, or `undefined` when the reply does
487
+ * not begin with a valid, consistent code.
488
+ * @since 0.6.0
489
+ */
490
+ function parseEnhancedSmtpStatusCode(replyCode, response) {
491
+ const match = /^([245])\.((?:0|[1-9][0-9]{0,2}))\.((?:0|[1-9][0-9]{0,2})) +/.exec(response);
492
+ if (match == null) return void 0;
493
+ const statusClass = Number(match[1]);
494
+ if (Math.trunc(replyCode / 100) !== statusClass) return void 0;
495
+ return {
496
+ code: `${match[1]}.${match[2]}.${match[3]}`,
497
+ class: statusClass,
498
+ subject: Number(match[2]),
499
+ detail: Number(match[3])
500
+ };
501
+ }
502
+
475
503
  //#endregion
476
504
  //#region src/smtp-connection.ts
477
505
  /**
@@ -1043,12 +1071,16 @@ var SmtpConnection = class {
1043
1071
  signal?.throwIfAborted();
1044
1072
  const rcptResponse = pipelining ? recipientResponses[index] : await this.sendCommand(recipientCommands[index], signal);
1045
1073
  if (rcptResponse.code === 421) throw new SmtpResponseError(`RCPT TO failed for ${recipient}: ${rcptResponse.message}`, rcptResponse.code, "RCPT TO", rcptResponse.message);
1046
- if (rcptResponse.code !== 250 && rcptResponse.code !== 251) rejectedRecipients.push({
1047
- recipient,
1048
- code: rcptResponse.code,
1049
- response: rcptResponse.message,
1050
- retryable: rcptResponse.code >= 400 && rcptResponse.code < 500
1051
- });
1074
+ if (rcptResponse.code !== 250 && rcptResponse.code !== 251) {
1075
+ const enhancedStatusCode = parseEnhancedSmtpStatusCode(rcptResponse.code, rcptResponse.message);
1076
+ rejectedRecipients.push({
1077
+ recipient,
1078
+ code: rcptResponse.code,
1079
+ response: rcptResponse.message,
1080
+ retryable: rcptResponse.code >= 400 && rcptResponse.code < 500,
1081
+ ...enhancedStatusCode == null ? {} : { enhancedStatusCode }
1082
+ });
1083
+ }
1052
1084
  }
1053
1085
  if (rejectedRecipients.length > 0 && rejectedRecipients.length === message.envelope.to.length) {
1054
1086
  const rejection = rejectedRecipients.find((item) => item.retryable) ?? rejectedRecipients[0];
@@ -2018,7 +2050,8 @@ function createSmtpFailure(message, error) {
2018
2050
  providerDetails: { missingCapability: error.missingCapability }
2019
2051
  });
2020
2052
  if (error instanceof SmtpResponseError) {
2021
- const classification = classifySmtpReply(error.code);
2053
+ const enhancedStatusCode = parseEnhancedSmtpStatusCode(error.code, error.response);
2054
+ const classification = classifySmtpReply(error.code, enhancedStatusCode);
2022
2055
  return (0, __upyo_core.createFailedReceipt)(message, {
2023
2056
  provider: "smtp",
2024
2057
  code: `smtp.${error.code}`,
@@ -2028,7 +2061,8 @@ function createSmtpFailure(message, error) {
2028
2061
  providerDetails: {
2029
2062
  command: error.command,
2030
2063
  response: error.response,
2031
- rejectedRecipients: error.rejectedRecipients
2064
+ rejectedRecipients: error.rejectedRecipients,
2065
+ ...enhancedStatusCode == null ? {} : { enhancedStatusCode }
2032
2066
  }
2033
2067
  });
2034
2068
  }
@@ -2040,14 +2074,23 @@ function createSmtpFailure(message, error) {
2040
2074
  function isReusableLocalFailure(error) {
2041
2075
  return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2042
2076
  }
2043
- function classifySmtpReply(code) {
2077
+ function classifySmtpReply(code, enhancedStatusCode) {
2078
+ const retryable = enhancedStatusCode == null ? code >= 400 && code < 500 : enhancedStatusCode.class === 4;
2079
+ if (enhancedStatusCode?.subject === 1 || enhancedStatusCode?.subject === 6) return {
2080
+ category: "validation",
2081
+ retryable
2082
+ };
2083
+ if (enhancedStatusCode?.subject === 4) return {
2084
+ category: "network",
2085
+ retryable
2086
+ };
2044
2087
  if (code >= 400 && code < 500) return {
2045
2088
  category: "service-unavailable",
2046
- retryable: true
2089
+ retryable
2047
2090
  };
2048
2091
  if (code >= 500 && code < 600) return {
2049
2092
  category: "rejected",
2050
- retryable: false
2093
+ retryable
2051
2094
  };
2052
2095
  return {
2053
2096
  category: "unknown",
@@ -2055,8 +2098,22 @@ function classifySmtpReply(code) {
2055
2098
  };
2056
2099
  }
2057
2100
 
2101
+ //#endregion
2102
+ //#region src/smtp-receipt.ts
2103
+ /**
2104
+ * Checks whether provider details came from an SMTP server response.
2105
+ *
2106
+ * @param value Provider details from an SMTP receipt error.
2107
+ * @returns Whether the value contains SMTP response details.
2108
+ * @since 0.6.0
2109
+ */
2110
+ function isSmtpResponseProviderDetails(value) {
2111
+ return value != null && typeof value === "object" && "command" in value && typeof value.command === "string" && "response" in value && typeof value.response === "string";
2112
+ }
2113
+
2058
2114
  //#endregion
2059
2115
  exports.SmtpAuthError = SmtpAuthError;
2060
2116
  exports.SmtpDsnUnsupportedError = SmtpDsnUnsupportedError;
2061
2117
  exports.SmtpDsnValidationError = SmtpDsnValidationError;
2062
- exports.SmtpTransport = SmtpTransport;
2118
+ exports.SmtpTransport = SmtpTransport;
2119
+ exports.isSmtpResponseProviderDetails = isSmtpResponseProviderDetails;
package/dist/index.d.cts CHANGED
@@ -510,6 +510,21 @@ declare class SmtpDsnUnsupportedError extends Error {
510
510
  /** @internal */
511
511
  //#endregion
512
512
  //#region src/smtp-receipt.d.ts
513
+ /**
514
+ * A machine-readable enhanced SMTP status code defined by RFC 3463.
515
+ *
516
+ * @since 0.6.0
517
+ */
518
+ interface SmtpEnhancedStatusCode {
519
+ /** The complete enhanced status code. */
520
+ readonly code: string;
521
+ /** The delivery result class: success, transient failure, or permanent failure. */
522
+ readonly class: 2 | 4 | 5;
523
+ /** The subject identifying the probable source of the condition. */
524
+ readonly subject: number;
525
+ /** The detail identifying the precise condition. */
526
+ readonly detail: number;
527
+ }
513
528
  /**
514
529
  * An SMTP envelope recipient that the server rejected during an otherwise
515
530
  * successful delivery.
@@ -525,7 +540,62 @@ interface SmtpRejectedRecipient {
525
540
  readonly response: string;
526
541
  /** Whether retrying delivery to this recipient may succeed. */
527
542
  readonly retryable: boolean;
543
+ /**
544
+ * The enhanced status code returned for this recipient, when valid.
545
+ *
546
+ * @since 0.6.0
547
+ */
548
+ readonly enhancedStatusCode?: SmtpEnhancedStatusCode;
528
549
  }
550
+ /**
551
+ * Provider details for a failure returned by an SMTP server.
552
+ *
553
+ * @since 0.6.0
554
+ */
555
+ interface SmtpResponseProviderDetails {
556
+ /** The SMTP command that failed. */
557
+ readonly command: string;
558
+ /** The text from the SMTP server's final reply line. */
559
+ readonly response: string;
560
+ /** Recipient-level failures collected for the transaction. */
561
+ readonly rejectedRecipients?: readonly SmtpRejectedRecipient[];
562
+ /** The enhanced status code in the server reply, when valid. */
563
+ readonly enhancedStatusCode?: SmtpEnhancedStatusCode;
564
+ }
565
+ /**
566
+ * Checks whether provider details came from an SMTP server response.
567
+ *
568
+ * @param value Provider details from an SMTP receipt error.
569
+ * @returns Whether the value contains SMTP response details.
570
+ * @since 0.6.0
571
+ */
572
+ declare function isSmtpResponseProviderDetails(value: unknown): value is SmtpResponseProviderDetails;
573
+ /**
574
+ * Provider details for a locally rejected oversized message.
575
+ *
576
+ * @since 0.6.0
577
+ */
578
+ interface SmtpMessageSizeProviderDetails {
579
+ /** The encoded message size in octets. */
580
+ readonly actualSize: number;
581
+ /** The server's advertised maximum size in octets. */
582
+ readonly maximumSize: string;
583
+ }
584
+ /**
585
+ * Provider details for a missing internationalization capability.
586
+ *
587
+ * @since 0.6.0
588
+ */
589
+ interface SmtpUtf8ProviderDetails {
590
+ /** The SMTP extension required by the message. */
591
+ readonly missingCapability: "SMTPUTF8" | "8BITMIME";
592
+ }
593
+ /**
594
+ * Provider-specific details attached to SMTP receipt errors.
595
+ *
596
+ * @since 0.6.0
597
+ */
598
+ type SmtpProviderDetails = SmtpResponseProviderDetails | SmtpMessageSizeProviderDetails | SmtpUtf8ProviderDetails;
529
599
  /**
530
600
  * A receipt returned by {@link SmtpTransport}.
531
601
  *
@@ -727,4 +797,4 @@ declare class SmtpAuthError extends Error {
727
797
  */
728
798
 
729
799
  //#endregion
730
- export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpReceipt, SmtpRejectedRecipient, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth };
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 };
package/dist/index.d.ts CHANGED
@@ -510,6 +510,21 @@ declare class SmtpDsnUnsupportedError extends Error {
510
510
  /** @internal */
511
511
  //#endregion
512
512
  //#region src/smtp-receipt.d.ts
513
+ /**
514
+ * A machine-readable enhanced SMTP status code defined by RFC 3463.
515
+ *
516
+ * @since 0.6.0
517
+ */
518
+ interface SmtpEnhancedStatusCode {
519
+ /** The complete enhanced status code. */
520
+ readonly code: string;
521
+ /** The delivery result class: success, transient failure, or permanent failure. */
522
+ readonly class: 2 | 4 | 5;
523
+ /** The subject identifying the probable source of the condition. */
524
+ readonly subject: number;
525
+ /** The detail identifying the precise condition. */
526
+ readonly detail: number;
527
+ }
513
528
  /**
514
529
  * An SMTP envelope recipient that the server rejected during an otherwise
515
530
  * successful delivery.
@@ -525,7 +540,62 @@ interface SmtpRejectedRecipient {
525
540
  readonly response: string;
526
541
  /** Whether retrying delivery to this recipient may succeed. */
527
542
  readonly retryable: boolean;
543
+ /**
544
+ * The enhanced status code returned for this recipient, when valid.
545
+ *
546
+ * @since 0.6.0
547
+ */
548
+ readonly enhancedStatusCode?: SmtpEnhancedStatusCode;
528
549
  }
550
+ /**
551
+ * Provider details for a failure returned by an SMTP server.
552
+ *
553
+ * @since 0.6.0
554
+ */
555
+ interface SmtpResponseProviderDetails {
556
+ /** The SMTP command that failed. */
557
+ readonly command: string;
558
+ /** The text from the SMTP server's final reply line. */
559
+ readonly response: string;
560
+ /** Recipient-level failures collected for the transaction. */
561
+ readonly rejectedRecipients?: readonly SmtpRejectedRecipient[];
562
+ /** The enhanced status code in the server reply, when valid. */
563
+ readonly enhancedStatusCode?: SmtpEnhancedStatusCode;
564
+ }
565
+ /**
566
+ * Checks whether provider details came from an SMTP server response.
567
+ *
568
+ * @param value Provider details from an SMTP receipt error.
569
+ * @returns Whether the value contains SMTP response details.
570
+ * @since 0.6.0
571
+ */
572
+ declare function isSmtpResponseProviderDetails(value: unknown): value is SmtpResponseProviderDetails;
573
+ /**
574
+ * Provider details for a locally rejected oversized message.
575
+ *
576
+ * @since 0.6.0
577
+ */
578
+ interface SmtpMessageSizeProviderDetails {
579
+ /** The encoded message size in octets. */
580
+ readonly actualSize: number;
581
+ /** The server's advertised maximum size in octets. */
582
+ readonly maximumSize: string;
583
+ }
584
+ /**
585
+ * Provider details for a missing internationalization capability.
586
+ *
587
+ * @since 0.6.0
588
+ */
589
+ interface SmtpUtf8ProviderDetails {
590
+ /** The SMTP extension required by the message. */
591
+ readonly missingCapability: "SMTPUTF8" | "8BITMIME";
592
+ }
593
+ /**
594
+ * Provider-specific details attached to SMTP receipt errors.
595
+ *
596
+ * @since 0.6.0
597
+ */
598
+ type SmtpProviderDetails = SmtpResponseProviderDetails | SmtpMessageSizeProviderDetails | SmtpUtf8ProviderDetails;
529
599
  /**
530
600
  * A receipt returned by {@link SmtpTransport}.
531
601
  *
@@ -727,4 +797,4 @@ declare class SmtpAuthError extends Error {
727
797
  */
728
798
 
729
799
  //#endregion
730
- export { DkimAlgorithm, DkimCanonicalization, DkimConfig, DkimSignature, DkimSigningFailureAction, OAuth2TokenProvider, SmtpAuth, SmtpAuthError, SmtpConfig, SmtpDsnNotification, SmtpDsnOptions, SmtpDsnRecipientOptions, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpOAuth2Auth, SmtpOAuth2RefreshAuth, SmtpOAuth2TokenAuth, SmtpReceipt, SmtpRejectedRecipient, SmtpTlsOptions, SmtpTransport, SmtpTransportOptions, SmtpUserPassAuth };
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 };
package/dist/index.js CHANGED
@@ -449,6 +449,34 @@ var OAuth2TokenManager = class {
449
449
  }
450
450
  };
451
451
 
452
+ //#endregion
453
+ //#region src/smtp-status-code.ts
454
+ /**
455
+ * Parses an enhanced SMTP status code from the beginning of reply text.
456
+ *
457
+ * RFC 2034 requires the enhanced class to agree with the three-digit SMTP
458
+ * reply class. RFC 3463 also prohibits leading zeroes and limits the subject
459
+ * and detail fields to three digits.
460
+ *
461
+ * @param replyCode The three-digit SMTP reply code.
462
+ * @param response The textual part of the SMTP reply.
463
+ * @returns The parsed enhanced status code, or `undefined` when the reply does
464
+ * not begin with a valid, consistent code.
465
+ * @since 0.6.0
466
+ */
467
+ function parseEnhancedSmtpStatusCode(replyCode, response) {
468
+ const match = /^([245])\.((?:0|[1-9][0-9]{0,2}))\.((?:0|[1-9][0-9]{0,2})) +/.exec(response);
469
+ if (match == null) return void 0;
470
+ const statusClass = Number(match[1]);
471
+ if (Math.trunc(replyCode / 100) !== statusClass) return void 0;
472
+ return {
473
+ code: `${match[1]}.${match[2]}.${match[3]}`,
474
+ class: statusClass,
475
+ subject: Number(match[2]),
476
+ detail: Number(match[3])
477
+ };
478
+ }
479
+
452
480
  //#endregion
453
481
  //#region src/smtp-connection.ts
454
482
  /**
@@ -1020,12 +1048,16 @@ var SmtpConnection = class {
1020
1048
  signal?.throwIfAborted();
1021
1049
  const rcptResponse = pipelining ? recipientResponses[index] : await this.sendCommand(recipientCommands[index], signal);
1022
1050
  if (rcptResponse.code === 421) throw new SmtpResponseError(`RCPT TO failed for ${recipient}: ${rcptResponse.message}`, rcptResponse.code, "RCPT TO", rcptResponse.message);
1023
- if (rcptResponse.code !== 250 && rcptResponse.code !== 251) rejectedRecipients.push({
1024
- recipient,
1025
- code: rcptResponse.code,
1026
- response: rcptResponse.message,
1027
- retryable: rcptResponse.code >= 400 && rcptResponse.code < 500
1028
- });
1051
+ if (rcptResponse.code !== 250 && rcptResponse.code !== 251) {
1052
+ const enhancedStatusCode = parseEnhancedSmtpStatusCode(rcptResponse.code, rcptResponse.message);
1053
+ rejectedRecipients.push({
1054
+ recipient,
1055
+ code: rcptResponse.code,
1056
+ response: rcptResponse.message,
1057
+ retryable: rcptResponse.code >= 400 && rcptResponse.code < 500,
1058
+ ...enhancedStatusCode == null ? {} : { enhancedStatusCode }
1059
+ });
1060
+ }
1029
1061
  }
1030
1062
  if (rejectedRecipients.length > 0 && rejectedRecipients.length === message.envelope.to.length) {
1031
1063
  const rejection = rejectedRecipients.find((item) => item.retryable) ?? rejectedRecipients[0];
@@ -1995,7 +2027,8 @@ function createSmtpFailure(message, error) {
1995
2027
  providerDetails: { missingCapability: error.missingCapability }
1996
2028
  });
1997
2029
  if (error instanceof SmtpResponseError) {
1998
- const classification = classifySmtpReply(error.code);
2030
+ const enhancedStatusCode = parseEnhancedSmtpStatusCode(error.code, error.response);
2031
+ const classification = classifySmtpReply(error.code, enhancedStatusCode);
1999
2032
  return createFailedReceipt(message, {
2000
2033
  provider: "smtp",
2001
2034
  code: `smtp.${error.code}`,
@@ -2005,7 +2038,8 @@ function createSmtpFailure(message, error) {
2005
2038
  providerDetails: {
2006
2039
  command: error.command,
2007
2040
  response: error.response,
2008
- rejectedRecipients: error.rejectedRecipients
2041
+ rejectedRecipients: error.rejectedRecipients,
2042
+ ...enhancedStatusCode == null ? {} : { enhancedStatusCode }
2009
2043
  }
2010
2044
  });
2011
2045
  }
@@ -2017,14 +2051,23 @@ function createSmtpFailure(message, error) {
2017
2051
  function isReusableLocalFailure(error) {
2018
2052
  return error instanceof SmtpMessageSizeError || error instanceof SmtpUtf8UnsupportedError || error instanceof SmtpDsnValidationError || error instanceof SmtpDsnUnsupportedError;
2019
2053
  }
2020
- function classifySmtpReply(code) {
2054
+ function classifySmtpReply(code, enhancedStatusCode) {
2055
+ const retryable = enhancedStatusCode == null ? code >= 400 && code < 500 : enhancedStatusCode.class === 4;
2056
+ if (enhancedStatusCode?.subject === 1 || enhancedStatusCode?.subject === 6) return {
2057
+ category: "validation",
2058
+ retryable
2059
+ };
2060
+ if (enhancedStatusCode?.subject === 4) return {
2061
+ category: "network",
2062
+ retryable
2063
+ };
2021
2064
  if (code >= 400 && code < 500) return {
2022
2065
  category: "service-unavailable",
2023
- retryable: true
2066
+ retryable
2024
2067
  };
2025
2068
  if (code >= 500 && code < 600) return {
2026
2069
  category: "rejected",
2027
- retryable: false
2070
+ retryable
2028
2071
  };
2029
2072
  return {
2030
2073
  category: "unknown",
@@ -2033,4 +2076,17 @@ function classifySmtpReply(code) {
2033
2076
  }
2034
2077
 
2035
2078
  //#endregion
2036
- export { SmtpAuthError, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpTransport };
2079
+ //#region src/smtp-receipt.ts
2080
+ /**
2081
+ * Checks whether provider details came from an SMTP server response.
2082
+ *
2083
+ * @param value Provider details from an SMTP receipt error.
2084
+ * @returns Whether the value contains SMTP response details.
2085
+ * @since 0.6.0
2086
+ */
2087
+ function isSmtpResponseProviderDetails(value) {
2088
+ return value != null && typeof value === "object" && "command" in value && typeof value.command === "string" && "response" in value && typeof value.response === "string";
2089
+ }
2090
+
2091
+ //#endregion
2092
+ export { SmtpAuthError, SmtpDsnUnsupportedError, SmtpDsnValidationError, SmtpTransport, isSmtpResponseProviderDetails };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upyo/smtp",
3
- "version": "0.6.0-dev.296",
3
+ "version": "0.6.0-dev.299",
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.296+edf6b8c1"
56
+ "@upyo/core": "0.6.0-dev.299+9e97f9f2"
57
57
  },
58
58
  "devDependencies": {
59
59
  "tsdown": "^0.12.7",