@bumail/smtp 0.9.0 → 0.11.0

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.
Files changed (39) hide show
  1. package/README.md +6 -4
  2. package/dist/chunks/{index-wc25ywta.js → index-byaw0g4k.js} +88 -3
  3. package/dist/chunks/{index-wc25ywta.js.map → index-byaw0g4k.js.map} +5 -3
  4. package/dist/client/dsn.d.ts +21 -0
  5. package/dist/client/dsn.d.ts.map +1 -0
  6. package/dist/client/index.js +98 -5
  7. package/dist/client/index.js.map +6 -5
  8. package/dist/client/options.d.ts +21 -0
  9. package/dist/client/options.d.ts.map +1 -1
  10. package/dist/client/settings.d.ts +2 -0
  11. package/dist/client/settings.d.ts.map +1 -1
  12. package/dist/client/transaction.d.ts +2 -0
  13. package/dist/client/transaction.d.ts.map +1 -1
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +120 -15
  17. package/dist/index.js.map +10 -8
  18. package/dist/protocol/command.d.ts +2 -0
  19. package/dist/protocol/command.d.ts.map +1 -1
  20. package/dist/protocol/orcpt.d.ts +27 -0
  21. package/dist/protocol/orcpt.d.ts.map +1 -0
  22. package/dist/protocol/xtext.d.ts +14 -0
  23. package/dist/protocol/xtext.d.ts.map +1 -0
  24. package/dist/server/commands.d.ts.map +1 -1
  25. package/dist/server/dsn-params.d.ts +21 -0
  26. package/dist/server/dsn-params.d.ts.map +1 -0
  27. package/dist/server/envelope.d.ts +5 -0
  28. package/dist/server/envelope.d.ts.map +1 -0
  29. package/dist/server/intake.d.ts.map +1 -1
  30. package/dist/server/options.d.ts +28 -0
  31. package/dist/server/options.d.ts.map +1 -1
  32. package/dist/server/state.d.ts +4 -1
  33. package/dist/server/state.d.ts.map +1 -1
  34. package/dist/server/transaction.d.ts +2 -2
  35. package/dist/server/transaction.d.ts.map +1 -1
  36. package/docs/guide.md +122 -5
  37. package/docs/roadmap.md +15 -5
  38. package/docs/troubleshooting.md +61 -6
  39. package/package.json +3 -3
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Encodes text as `xtext` (RFC 3461 §4), the form DSN parameter values take
3
+ * on the wire: every byte of its UTF-8 form outside `!` to `~` (33 to 126),
4
+ * and `+` and `=`, becomes `+` and two upper-case hex digits.
5
+ */
6
+ export declare function encodeXtext(text: string): string;
7
+ /**
8
+ * Decodes `xtext` (RFC 3461 §4) read as UTF-8, or `undefined` when it is
9
+ * not xtext: a `+` not followed by two upper-case hex digits, a character
10
+ * outside `!` to `~` that is not encoded, a bare `=`, or bytes that are not
11
+ * valid UTF-8.
12
+ */
13
+ export declare function decodeXtext(value: string): string | undefined;
14
+ //# sourceMappingURL=xtext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xtext.d.ts","sourceRoot":"","sources":["../../src/protocol/xtext.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAShD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAwB7D"}
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/server/commands.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAuG/C,0EAA0E;AAC1E,wBAAsB,UAAU,CAC/B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/server/commands.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAwG/C,0EAA0E;AAC1E,wBAAsB,UAAU,CAC/B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
@@ -0,0 +1,21 @@
1
+ import type { DsnNotify, EnvelopeRecipient } from './options';
2
+ import type { Transaction } from './state';
3
+ /** The DSN parameters (RFC 3461 §4) MAIL FROM takes. */
4
+ export declare const MAIL_DSN: Set<string>;
5
+ /** The DSN parameters (RFC 3461 §4) RCPT TO takes. */
6
+ export declare const RCPT_DSN: Set<string>;
7
+ /**
8
+ * Reads one DSN parameter of MAIL FROM into the transaction under way.
9
+ * The text of the `501` to answer when the value is wrong, else `undefined`.
10
+ */
11
+ export declare function readMailDsn(into: Pick<Transaction, 'ret' | 'envid'>, key: string, value: string, repeated: readonly string[]): string | undefined;
12
+ /**
13
+ * Reads one DSN parameter of RCPT TO into `into`; `smtputf8` is whether the
14
+ * transaction uses SMTPUTF8 (the form of a `utf-8` ORCPT). The text of the `501` to
15
+ * answer when the value is wrong, else `undefined`.
16
+ */
17
+ export declare function readRcptDsn(into: {
18
+ notify?: 'NEVER' | readonly DsnNotify[];
19
+ orcpt?: EnvelopeRecipient['orcpt'];
20
+ }, key: string, value: string, repeated: readonly string[], smtputf8?: boolean): string | undefined;
21
+ //# sourceMappingURL=dsn-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsn-params.d.ts","sourceRoot":"","sources":["../../src/server/dsn-params.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,wDAAwD;AACxD,eAAO,MAAM,QAAQ,aAA4B,CAAC;AAClD,sDAAsD;AACtD,eAAO,MAAM,QAAQ,aAA+B,CAAC;AAoDrD;;;GAGG;AACH,wBAAgB,WAAW,CAC1B,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,OAAO,CAAC,EACxC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,MAAM,EAAE,GACzB,MAAM,GAAG,SAAS,CAYpB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,IAAI,EAAE;IACL,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,SAAS,EAAE,CAAC;IACxC,KAAK,CAAC,EAAE,iBAAiB,CAAC,OAAO,CAAC,CAAC;CACnC,EACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,QAAQ,UAAQ,GACd,MAAM,GAAG,SAAS,CAYpB"}
@@ -0,0 +1,5 @@
1
+ import type { ReceivedMessage } from './options';
2
+ import type { Transaction } from './state';
3
+ /** The envelope of a message as `onData` is told it, from the transaction that ended in DATA. */
4
+ export declare function envelopeOf(transaction: Transaction): ReceivedMessage['envelope'];
5
+ //# sourceMappingURL=envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.d.ts","sourceRoot":"","sources":["../../src/server/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,iGAAiG;AACjG,wBAAgB,UAAU,CACzB,WAAW,EAAE,WAAW,GACtB,eAAe,CAAC,UAAU,CAAC,CAU7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/server/intake.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAS,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAgB/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAK3C;;;;;;;;;;;;GAYG;AACH,qBAAa,MAAM;;IAClB,QAAQ,CAAC,EAAE,SAAsD;gBAoBrD,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW;IA0E5D,iEAAiE;IAC3D,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC;IAmB5E,0CAA0C;IACpC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC;IAuB9B,uEAAuE;IACvE,KAAK,IAAI,IAAI;CA2Eb"}
1
+ {"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/server/intake.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,KAAK,EAAS,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiB/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAK3C;;;;;;;;;;;;GAYG;AACH,qBAAa,MAAM;;IAClB,QAAQ,CAAC,EAAE,SAAsD;gBAoBrD,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW;IAqE5D,iEAAiE;IAC3D,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC;IAmB5E,0CAA0C;IACpC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC;IAuB9B,uEAAuE;IACvE,KAAK,IAAI,IAAI;CA2Eb"}
@@ -20,6 +20,28 @@ export interface Session {
20
20
  */
21
21
  readonly data: Record<string, unknown>;
22
22
  }
23
+ /** What a recipient asked to be told (RFC 3461 §4.1): `NEVER`, or the events among these. */
24
+ export type DsnNotify = 'SUCCESS' | 'FAILURE' | 'DELAY';
25
+ /**
26
+ * One recipient of a message, with the DSN parameters of its `RCPT TO`
27
+ * (RFC 3461 §4.1, §4.2). The server only passes them on: the app honours
28
+ * them when it relays the message or generates a DSN.
29
+ */
30
+ export interface EnvelopeRecipient {
31
+ /** The same address as the matching entry of `Envelope.to`. */
32
+ readonly address: string;
33
+ /**
34
+ * `NOTIFY=`: `'NEVER'`, or the events, in the order given, with no
35
+ * duplicate. Absent when the client sent none: the default is a DSN on
36
+ * failure, delay and success never (RFC 3461 §4.1).
37
+ */
38
+ readonly notify?: 'NEVER' | readonly DsnNotify[];
39
+ /** `ORCPT=`: the original recipient, its `type` as sent (`rfc822`) and its `address` decoded from xtext. */
40
+ readonly orcpt?: {
41
+ readonly type: string;
42
+ readonly address: string;
43
+ };
44
+ }
23
45
  /** The sender and recipients of one message. */
24
46
  export interface Envelope {
25
47
  /** `''` for the null reverse-path `<>`: a bounce. */
@@ -30,6 +52,12 @@ export interface Envelope {
30
52
  readonly smtputf8: boolean;
31
53
  /** `7BIT` or `8BITMIME`, as MAIL FROM's BODY= said; `7BIT` when it said nothing. */
32
54
  readonly body: '7BIT' | '8BITMIME';
55
+ /** `RET=` of MAIL FROM (RFC 3461 §4.3), upper case: whether a DSN returns the whole message or its headers. Absent when not sent. */
56
+ readonly ret?: 'FULL' | 'HDRS';
57
+ /** `ENVID=` of MAIL FROM (RFC 3461 §4.4), decoded from xtext: at most 100 printable ASCII characters. Absent when not sent. */
58
+ readonly envid?: string;
59
+ /** The recipients accepted, in the order of `to`, each with its `NOTIFY` and `ORCPT`. */
60
+ readonly recipients: readonly EnvelopeRecipient[];
33
61
  }
34
62
  /** A message on its way in, handed to `onData` as the client sends it. */
35
63
  export interface ReceivedMessage {
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/server/options.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,11 @@
1
- import type { Envelope } from './options';
1
+ import type { Envelope, EnvelopeRecipient } from './options';
2
2
  /** A mail transaction in progress (RFC 5321 §3.3). */
3
3
  export interface Transaction {
4
4
  from?: string;
5
5
  to: string[];
6
+ recipients: EnvelopeRecipient[];
7
+ ret?: Envelope['ret'];
8
+ envid?: string;
6
9
  smtputf8: boolean;
7
10
  body: Envelope['body'];
8
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/server/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAChB,SAAS,GACT,MAAM,GACN,YAAY,GACZ,iBAAiB,GACjB,qBAAqB,CAAC;AAEzB,kDAAkD;AAClD,MAAM,WAAW,KAAK;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,gBAAgB,QAAO,WAIlC,CAAC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/server/state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE7D,sDAAsD;AACtD,MAAM,WAAW,WAAW;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,UAAU,EAAE,iBAAiB,EAAE,CAAC;IAChC,GAAG,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACvB;AAED,6CAA6C;AAC7C,MAAM,MAAM,OAAO,GAChB,SAAS,GACT,MAAM,GACN,YAAY,GACZ,iBAAiB,GACjB,qBAAqB,CAAC;AAEzB,kDAAkD;AAClD,MAAM,WAAW,KAAK;IACrB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,gBAAgB,QAAO,WAKlC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import type { Connection } from './connection';
2
- /** `MAIL FROM:<path> [SIZE=] [BODY=] [SMTPUTF8] [AUTH=]` (RFC 5321 §4.1.1.2). */
2
+ /** `MAIL FROM:<path> [SIZE=] [BODY=] [SMTPUTF8] [AUTH=] [RET=] [ENVID=]` (RFC 5321 §4.1.1.2, RFC 3461 §4.3, §4.4). */
3
3
  export declare function mail(connection: Connection, argument: string): Promise<void>;
4
- /** `RCPT TO:<path>` (RFC 5321 §4.1.1.3), where relaying is refused without AUTH. */
4
+ /** `RCPT TO:<path> [NOTIFY=] [ORCPT=]` (RFC 5321 §4.1.1.3, RFC 3461 §4.1, §4.2), where relaying is refused without AUTH. */
5
5
  export declare function rcpt(connection: Connection, argument: string): Promise<void>;
6
6
  //# sourceMappingURL=transaction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/server/transaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAS/C,iFAAiF;AACjF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAuDf;AAED,oFAAoF;AACpF,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAyCf"}
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../../src/server/transaction.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAY/C,sHAAsH;AACtH,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAkEf;AAED,4HAA4H;AAC5H,wBAAsB,IAAI,CACzB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA0Df"}
package/docs/guide.md CHANGED
@@ -192,7 +192,9 @@ Every reply the server sends of its own:
192
192
  | `503 5.5.1 Send MAIL first` | RCPT or DATA before MAIL |
193
193
  | `554 5.5.1 No valid recipients` | DATA with no recipient accepted; what the client pipelined behind it in the same write is dropped, not run as commands |
194
194
  | `501 5.5.4 Syntax: MAIL FROM:<address>` / `RCPT TO:<address>` / `DATA` | the command's syntax is wrong |
195
- | `555 5.5.4 <PARAM> is not supported` | an ESMTP parameter other than SIZE, BODY, SMTPUTF8 and AUTH on MAIL, or any parameter on RCPT |
195
+ | `555 5.5.4 <PARAM> is not supported` | an ESMTP parameter other than SIZE, BODY, SMTPUTF8, AUTH, RET and ENVID on MAIL, or other than NOTIFY and ORCPT on RCPT |
196
+ | `501 5.5.4 Syntax: RET=…` / `ENVID=…` / `NOTIFY=…` / `ORCPT=…` | a DSN parameter with a malformed value (RFC 3461 §4) |
197
+ | `501 5.5.4 <PARAM> given twice` | a DSN parameter repeated on one command |
196
198
  | `555 5.5.4 <PARAM> needs EHLO` | a parameter after HELO |
197
199
  | `501 5.5.4 Syntax: SIZE=<bytes>` | SIZE is not a number |
198
200
  | `501 5.5.4 BODY is 7BIT or 8BITMIME` | another BODY value |
@@ -463,6 +465,20 @@ export interface Envelope {
463
465
  readonly smtputf8: boolean;
464
466
  /** `7BIT` or `8BITMIME`, as MAIL FROM's BODY= said; `7BIT` when it said nothing. */
465
467
  readonly body: '7BIT' | '8BITMIME';
468
+ /** MAIL FROM's RET= (RFC 3461 §4.3); absent when not sent. */
469
+ readonly ret?: 'FULL' | 'HDRS';
470
+ /** MAIL FROM's ENVID=, decoded from xtext (RFC 3461 §4.4); absent when not sent. */
471
+ readonly envid?: string;
472
+ /** The recipients accepted, in the order of `to`, with their DSN parameters. */
473
+ readonly recipients: readonly EnvelopeRecipient[];
474
+ }
475
+
476
+ export interface EnvelopeRecipient {
477
+ readonly address: string; // the matching entry of `to`
478
+ /** NOTIFY= (RFC 3461 §4.1): `'NEVER'` or the events given; absent when not sent. */
479
+ readonly notify?: 'NEVER' | readonly ('SUCCESS' | 'FAILURE' | 'DELAY')[];
480
+ /** ORCPT= (RFC 3461 §4.2): the type as sent and the address decoded (xtext, or RFC 6533 for `utf-8`). */
481
+ readonly orcpt?: { readonly type: string; readonly address: string };
466
482
  }
467
483
  ```
468
484
 
@@ -636,6 +652,103 @@ const server = createSmtpServer({
636
652
  await server.listen({ port: 25 });
637
653
  ```
638
654
 
655
+ ## Delivery status notifications (DSN)
656
+
657
+ The server advertises `DSN` (RFC 3461) in its EHLO reply, always (no option to
658
+ turn it off). A client may then send `RET=FULL|HDRS` and `ENVID=<xtext>` on
659
+ `MAIL FROM`, and `NOTIFY=NEVER` (or a comma list of `SUCCESS`, `FAILURE`,
660
+ `DELAY`) and `ORCPT=<addr-type>;<xtext>` on `RCPT TO`. The server checks
661
+ their syntax, decodes them (`rfc822` as xtext, `utf-8` per RFC 6533, below)
662
+ and puts them on the `Envelope` `onData`
663
+ receives: `ret` and `envid`, and for each accepted recipient `notify` and
664
+ `orcpt` in `recipients`. `to` is unchanged.
665
+
666
+ `ORCPT` keeps the type as sent. `utf-8`, matched in any case, is read per RFC
667
+ 6533; every other type, `rfc822` included, is xtext (RFC 3461 §4, §5.1(b)). `utf-8`
668
+ (RFC 6533 §3) is `utf-8-addr-xtext`: printable ASCII as itself, and
669
+ `\x{HEX}` (2 to 6 hexadecimal digits) for a non-ASCII character and for
670
+ space, `\`, `+` and `=`; when the transaction used `SMTPUTF8` on `MAIL FROM`
671
+ it may also be `utf-8-addr-unitext`, with the non-ASCII characters as raw
672
+ UTF-8. The address in `orcpt.address` is decoded in both cases. A raw
673
+ non-ASCII address without `SMTPUTF8`, or a malformed or out-of-range escape
674
+ (or one for NUL or a control character RFC 6533's grammar has no escape for,
675
+ `0A` to `0F` and `1A` to `1F`) is `501 5.5.4`. Escapes are accepted leniently:
676
+ leading zeros and escaped printable ASCII, which RFC 6533's HEXPOINT does not
677
+ produce. The 500 characters of a `utf-8` value count as sent on the wire.
678
+
679
+ The server does nothing else with them. Once it advertises DSN, the app that
680
+ receives these parameters must honour them, or the client is told something
681
+ untrue:
682
+
683
+ - An app that **relays** the message (a queue, a smarthost) passes them on
684
+ to the next hop, or generates the DSN itself when the next hop does not
685
+ advertise DSN.
686
+ - An app that **delivers locally** generates a DSN for a recipient that asked
687
+ for `SUCCESS` and was delivered, returning the message or only its
688
+ headers as `ret` says, and puts `envid` and `orcpt` in the report.
689
+ - `NOTIFY=NEVER` means no DSN for that recipient, a bounce included.
690
+
691
+ ```ts
692
+ import { createSmtpServer } from '@bumail/smtp';
693
+
694
+ createSmtpServer({
695
+ hostname: 'mx.example.com',
696
+ localDomains: ['example.com'],
697
+ async onData({ envelope }) {
698
+ for (const { address, notify, orcpt } of envelope.recipients) {
699
+ if (notify === 'NEVER') continue; // no DSN for this one, ever
700
+ // deliver to `address`; report to `envelope.from` as `notify` asks,
701
+ // quoting `envelope.envid` and `orcpt?.address`
702
+ }
703
+ },
704
+ });
705
+ ```
706
+
707
+ A malformed or repeated DSN parameter is answered `501 5.5.4`; a parameter
708
+ after `HELO` gets `555 <PARAM> needs EHLO`.
709
+
710
+ ### Sending DSN parameters
711
+
712
+ `sendMail` takes `ret`, `envid` and `recipients` (the shape of
713
+ `Envelope.recipients`: `address`, `notify`, `orcpt`, matched to `to` by
714
+ address), so a received envelope can be passed through when a relay hands the
715
+ message on. They are written only when the next hop's EHLO lists `DSN`, with
716
+ `envid` and the `orcpt` address xtext-encoded (RFC 3461 §4). A `utf-8`
717
+ `orcpt` is encoded per RFC 6533 §3: `utf-8-addr-unitext` (non-ASCII kept as
718
+ UTF-8) when the transaction uses `SMTPUTF8`, else `utf-8-addr-xtext`
719
+ (`\x{HEX}`). An `orcpt` of another type is relayed as `type;xtext(address)`, the
720
+ type's case kept (RFC 3461 §5.2(d)). An `orcpt` whose address is not ASCII (type `rfc822`), holds a
721
+ lone surrogate (any type) or has no RFC 6533 escape (`utf-8`), or a `utf-8` one
722
+ over 500 characters as sent, is left out of the command, not refused. One of
723
+ another type over 500 characters as sent is refused with `INVALID_OPTION`.
724
+ A relay never fails on what a received envelope carries. The type is written as given. When the next hop lists no `DSN` nothing is sent, the delivery goes
725
+ through as usual, and the result says `dsnOffered: false`: the relay must then
726
+ generate the DSNs itself (RFC 3461 §5.2.2).
727
+
728
+ ```ts
729
+ import { sendMail } from '@bumail/smtp/client';
730
+
731
+ const result = await sendMail(message, {
732
+ host: 'smtp.relay.example',
733
+ from: 'a@example.com',
734
+ to: ['b@example.net'],
735
+ ret: 'HDRS',
736
+ envid: 'q-1042',
737
+ recipients: [{ address: 'b@example.net', notify: ['FAILURE', 'DELAY'] }],
738
+ });
739
+ if (!result.dsnOffered) {
740
+ // the next hop never saw NOTIFY: send the 'relayed' DSN here
741
+ }
742
+ ```
743
+
744
+ `dsnOffered` is the server listing `DSN` in EHLO; it does not prove the next
745
+ hop will send a DSN. These are `INVALID_OPTION`: a wrong `ret`, `notify` or
746
+ `orcpt` shape; an `envid` over 100 characters once xtext-encoded (RFC 3461
747
+ §4.4); an `orcpt` over 500 characters once encoded (§4.2; for a `utf-8` `orcpt`, the
748
+ unitext form: raw UTF-8 octets, `\x{HH}` = 6), whose type is not a
749
+ token, or whose address is empty; a `recipients` entry not in `to`, or named
750
+ twice.
751
+
639
752
  ## Delivering into @bumail/store
640
753
 
641
754
  [`@bumail/store`](https://github.com/softistx/bumail/tree/develop/packages/store) keeps
@@ -1893,6 +2006,8 @@ console.log(chunk.done, reader.bareLineBreaks);
1893
2006
  | SIZE | RFC 1870 |
1894
2007
  | PIPELINING | RFC 2920 |
1895
2008
  | 8BITMIME | RFC 6152 |
2009
+ | DSN parameters (`RET`, `ENVID`, `NOTIFY`, `ORCPT`) and xtext | RFC 3461 §4 |
2010
+ | The `utf-8` `ORCPT` address type | RFC 6533 §3 |
1896
2011
  | SMTPUTF8 | RFC 6531 |
1897
2012
  | ENHANCEDSTATUSCODES and the codes themselves | RFC 2034, RFC 3463 |
1898
2013
  | STARTTLS | RFC 3207 |
@@ -1914,9 +2029,9 @@ each one comes from.
1914
2029
 
1915
2030
  ## What is not implemented
1916
2031
 
1917
- - **DSN** (RFC 3461). `RET`, `ENVID`, `NOTIFY` and `ORCPT` are refused with
1918
- `555 5.5.4 <PARAM> is not supported`; DSN is not advertised, so a
1919
- conforming client does not send them.
2032
+ - **Generating DSNs.** The server advertises DSN and hands the parameters to
2033
+ `onData` (see [Delivery status notifications](#delivery-status-notifications-dsn)),
2034
+ but it never writes a notification: honouring them is the app's job.
1920
2035
  - **CHUNKING** (RFC 3030). `BDAT` is answered `500 5.5.2 Command
1921
2036
  unrecognized`; a client sends DATA instead.
1922
2037
  - **A queue.** The client tries a destination once and reports;
@@ -1924,7 +2039,9 @@ each one comes from.
1924
2039
  - **Connection reuse.** The client opens one connection per message, and
1925
2040
  sends no `RSET`. Several messages to one destination over one session is
1926
2041
  on the [roadmap](roadmap.md).
1927
- - **DSN in the client.** It sends no `RET`, `ENVID`, `NOTIFY` or `ORCPT`.
2042
+ - **A non-ASCII `ORCPT` of type `rfc822` in the client**: RFC 6533 defines
2043
+ `utf-8` for it, which both sides handle; the client leaves a non-ASCII
2044
+ `rfc822` address out of `RCPT TO`. Any other type is relayed as xtext.
1928
2045
  - **CHUNKING in the client.** It sends DATA, never `BDAT`.
1929
2046
  - **DANE and MTA-STS.** By MX, TLS is opportunistic; a policy that makes
1930
2047
  it required for a domain is for later.
package/docs/roadmap.md CHANGED
@@ -6,22 +6,22 @@ number on it.
6
6
 
7
7
  ## Now
8
8
 
9
- Nothing in progress.
9
+ Nothing is under way besides what is marked shipped below.
10
10
 
11
11
  ## Next
12
12
 
13
13
  - **Connection reuse in the client** — several messages to one
14
14
  destination over one session (`RSET` between them), for a queue that
15
15
  delivers in batches. Today each `sendMail` opens its own connection.
16
- - **DSN** (RFC 3461) — `RET`, `ENVID`, `NOTIFY` and `ORCPT` accepted and
17
- handed to the app, so a sender can ask for delivery status
18
- notifications. Today they are refused with `555 5.5.4`, and the client
19
- sends none.
20
16
  - **Delivery into `@bumail/store`, ready-made** — an `onData` that puts
21
17
  each message in its recipients' inboxes, instead of the few lines the
22
18
  [guide](guide.md#delivering-into-bumailstore) shows today. The store
23
19
  itself has landed; only the helper is to come.
24
20
 
21
+ ## Later
22
+
23
+ Nothing yet.
24
+
25
25
  ## Not planned
26
26
 
27
27
  - **Relaying without AUTH** — not as a default, not as an option. A
@@ -34,6 +34,16 @@ Nothing in progress.
34
34
 
35
35
  ### Unreleased — merged, not yet published
36
36
 
37
+ - **UTF-8 `ORCPT` (RFC 6533)**: the server decodes `ORCPT=utf-8;…`
38
+ (`utf-8-addr-xtext` with `\x{HEX}`, and raw UTF-8 under `SMTPUTF8`); the
39
+ client sends a
40
+ `utf-8` `ORCPT` in the form the transaction allows instead of leaving it
41
+ out. The reports built from it, in `@bumail/queue`, are not part of this.
42
+ - **DSN requests (RFC 3461)**: the server accepts `RET`, `ENVID`, `NOTIFY`
43
+ and `ORCPT` and hands them to the app, and always lists `DSN` in its
44
+ `EHLO` reply, so these parameters are no longer refused with `555 5.5.4`
45
+ (a client that relied on that refusal has to adapt). The client sends
46
+ them when the next hop lists `DSN`, and its result reports `dsnOffered`.
37
47
  - **The envelope on an `afterData` error**: `SmtpError.accepted` and
38
48
  `rejected` list the recipients the server took and refused at `RCPT TO`
39
49
  when the session failed after the final dot.
@@ -63,6 +63,8 @@ A reply carries its enhanced status code (`5.7.1`, RFC 3463) only after
63
63
  - [`503 5.5.1 TLS already active`](#503-551-tls-already-active)
64
64
  - [`501 5.5.4 BODY is 7BIT or 8BITMIME`](#501-554-body-is-7bit-or-8bitmime)
65
65
  - [`555 5.5.4 … is not supported`](#555-554--is-not-supported)
66
+ - [`501 5.5.4 Syntax: RET=…`, `ENVID=…`, `NOTIFY=…`, `ORCPT=…`](#501-554-syntax-ret-envid-notify-orcpt)
67
+ - [`501 5.5.4 … given twice`](#501-554--given-twice)
66
68
  - [`555 … needs EHLO`](#555--needs-ehlo)
67
69
  - [`553 5.6.7 A non-ASCII address needs SMTPUTF8`](#553-567-a-non-ascii-address-needs-smtputf8)
68
70
  - [`500 5.5.6 Line too long`](#500-556-line-too-long)
@@ -1136,14 +1138,67 @@ S: 250 2.1.0 OK
1136
1138
  ### `555 5.5.4 … is not supported`
1137
1139
 
1138
1140
  **When**: `MAIL FROM` with a parameter other than `SIZE`, `BODY`,
1139
- `SMTPUTF8` and `AUTH`, such as `RET` or `ENVID`; or `RCPT TO` with any
1140
- parameter, such as `NOTIFY` or `ORCPT`; or `SMTPUTF8` given a value.
1141
+ `SMTPUTF8`, `AUTH`, `RET` and `ENVID`; or `RCPT TO` with one other than
1142
+ `NOTIFY` and `ORCPT`; or `SMTPUTF8` given a value.
1141
1143
 
1142
- **Why**: the server supports only the extensions its EHLO lists. Delivery
1143
- status notifications (DSN, RFC 3461) are not among them.
1144
+ **Why**: the server supports only the extensions its EHLO lists. The DSN
1145
+ parameters (RFC 3461) are accepted, each on its own command: `RET` on
1146
+ `RCPT TO`, or `NOTIFY` on `MAIL FROM`, is refused here.
1144
1147
 
1145
- **Fix**, as a client: send only the parameters the EHLO reply announces;
1146
- turn off DSN options (`NOTIFY`, `RET`, `ENVID`, `ORCPT`) for this server.
1148
+ **Fix**, as a client: send only the parameters the EHLO reply announces,
1149
+ each on the command it belongs to.
1150
+
1151
+ ### `INVALID_OPTION` from `sendMail`: `envid …` or `orcpt …`
1152
+
1153
+ **When**: `sendMail` is given a DSN option it refuses, before any connection.
1154
+ The messages: `envid must be 1 to 100 printable ASCII characters, at most 100
1155
+ once encoded as xtext`; `orcpt of <address> must be { type, address }, the type
1156
+ a token`; `orcpt.address of <address> must be a non-empty string`; `orcpt of
1157
+ <address> is over 500 characters once encoded`.
1158
+
1159
+ **Why**: RFC 3461 §4.4 and §4.2 cap the parameters as sent, so a character that
1160
+ xtext writes as `+XX` counts three (for a `utf-8` `orcpt`, the unitext form:
1161
+ raw UTF-8 octets, `\x{HH}` = 6; for another type, the type, the `;` and the xtext form).
1162
+
1163
+ **Fix**: shorten the value, or give the type as letters, digits and hyphens.
1164
+
1165
+ ```ts
1166
+ await sendMail(message, { host, from, to, envid: 'q-1042' }); // not 'q 1042 …' past 100 encoded
1167
+ ```
1168
+
1169
+ ### `501 5.5.4 Syntax: RET=…`, `ENVID=…`, `NOTIFY=…`, `ORCPT=…`
1170
+
1171
+ **When**: a DSN parameter has a value the RFC does not allow: `RET` other
1172
+ than `FULL` or `HDRS`; `ENVID` empty, longer than 100 characters as sent,
1173
+ not printable ASCII or not xtext; `NOTIFY` mixing `NEVER` with another
1174
+ event, naming an event twice or an unknown one; `ORCPT` with no
1175
+ `addr-type;` prefix, an address that does not decode (not xtext for
1176
+ `rfc822`; for `utf-8`, RFC 6533's `\x{HEX}` form, with raw UTF-8 only when
1177
+ the transaction used `SMTPUTF8`), or more than 500 characters as sent (RFC
1178
+ 3461 §4.2).
1179
+
1180
+ **Why**: RFC 3461 §4. The server refuses the command rather than hand a
1181
+ wrong value to `onData`.
1182
+
1183
+ **Fix**, as a client: encode the value as xtext (`+` and `=`, spaces and
1184
+ non-ASCII bytes as `+XX`), and send `NOTIFY=NEVER` alone. For
1185
+ `ORCPT=utf-8;` use RFC 6533 instead: `\x{E9}` for `é`, and `\x{2B}` for `+`
1186
+ (not `+2B`), or raw UTF-8 after `MAIL FROM … SMTPUTF8`.
1187
+
1188
+ ```text
1189
+ C: RCPT TO:<bob@example.com> NOTIFY=SUCCESS,FAILURE ORCPT=rfc822;bob+2Btag@example.com
1190
+ S: 250 2.1.5 OK
1191
+ C: RCPT TO:<bob@example.com> ORCPT=utf-8;\x{E9}l\x{E8}ve@example.com
1192
+ S: 250 2.1.5 OK
1193
+ ```
1194
+
1195
+ ### `501 5.5.4 … given twice`
1196
+
1197
+ **When**: `RET`, `ENVID`, `NOTIFY` or `ORCPT` appears twice on one command.
1198
+
1199
+ **Why**: each DSN parameter may be given once (RFC 3461 §4).
1200
+
1201
+ **Fix**, as a client: send it once; join `NOTIFY` events in one list.
1147
1202
 
1148
1203
  ### `555 … needs EHLO`
1149
1204
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bumail/smtp",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "An SMTP server on Bun.listen — RFC 5321 with PIPELINING, SIZE, 8BITMIME, SMTPUTF8, STARTTLS and AUTH after TLS, policy hooks, and no open relay — and a client that delivers a message to a smarthost or by MX",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -47,9 +47,9 @@
47
47
  ]
48
48
  },
49
49
  "devDependencies": {
50
- "@bumail/auth": "^0.4.1",
50
+ "@bumail/auth": "^0.5.0",
51
51
  "@bumail/dns": "^0.2.1",
52
- "@bumail/store": "^0.4.2",
52
+ "@bumail/store": "^0.6.0",
53
53
  "@types/bun": "^1.4.2"
54
54
  },
55
55
  "peerDependencies": {