@upyo/smtp 0.6.0-dev.298 → 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 +35 -0
- package/dist/index.cjs +69 -12
- package/dist/index.d.cts +71 -1
- package/dist/index.d.ts +71 -1
- package/dist/index.js +68 -12
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@ Features
|
|
|
28
28
|
- Multiple recipients (To, CC, BCC)
|
|
29
29
|
- SMTP PIPELINING for faster multi-recipient delivery
|
|
30
30
|
- SMTP SIZE declaration and advertised-limit checks
|
|
31
|
+
- 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)
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
|
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
|
|
2089
|
+
retryable
|
|
2047
2090
|
};
|
|
2048
2091
|
if (code >= 500 && code < 600) return {
|
|
2049
2092
|
category: "rejected",
|
|
2050
|
-
retryable
|
|
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)
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
|
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
|
|
2066
|
+
retryable
|
|
2024
2067
|
};
|
|
2025
2068
|
if (code >= 500 && code < 600) return {
|
|
2026
2069
|
category: "rejected",
|
|
2027
|
-
retryable
|
|
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
|
-
|
|
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.
|
|
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.
|
|
56
|
+
"@upyo/core": "0.6.0-dev.299+9e97f9f2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"tsdown": "^0.12.7",
|