@mavogel/awscdk-rootmail 0.1.34 → 0.1.35

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 (120) hide show
  1. package/.jsii +8 -8
  2. package/lib/rootmail.js +1 -1
  3. package/lib/ses-receive.js +1 -1
  4. package/node_modules/@aws-sdk/checksums/dist-cjs/submodules/sha/index.js +464 -40
  5. package/node_modules/@aws-sdk/checksums/dist-cjs/submodules/xxh/index.js +914 -0
  6. package/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/index.browser.js +2 -0
  7. package/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/index.js +3 -0
  8. package/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/sha512/Sha512.js +71 -0
  9. package/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/sha512/Sha512Js.js +276 -0
  10. package/node_modules/@aws-sdk/checksums/dist-es/submodules/sha/sha512/Sha512WebCrypto.js +76 -0
  11. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/U64.js +190 -0
  12. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/Xxh3.js +274 -0
  13. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/index.js +3 -0
  14. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/xxhash3_128/Xxh3_128.js +179 -0
  15. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/xxhash3_64/Xxh3_64.js +117 -0
  16. package/node_modules/@aws-sdk/checksums/dist-es/submodules/xxh/xxhash64/Xxh64.js +150 -0
  17. package/node_modules/@aws-sdk/checksums/dist-types/submodules/sha/index.browser.d.ts +2 -0
  18. package/node_modules/@aws-sdk/checksums/dist-types/submodules/sha/index.d.ts +3 -0
  19. package/node_modules/@aws-sdk/checksums/dist-types/submodules/sha/sha512/Sha512.d.ts +17 -0
  20. package/node_modules/@aws-sdk/checksums/dist-types/submodules/sha/sha512/Sha512Js.d.ts +29 -0
  21. package/node_modules/@aws-sdk/checksums/dist-types/submodules/sha/sha512/Sha512WebCrypto.d.ts +22 -0
  22. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/U64.d.ts +110 -0
  23. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/Xxh3.d.ts +139 -0
  24. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/index.d.ts +3 -0
  25. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/xxhash3_128/Xxh3_128.d.ts +37 -0
  26. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/xxhash3_64/Xxh3_64.d.ts +27 -0
  27. package/node_modules/@aws-sdk/checksums/dist-types/submodules/xxh/xxhash64/Xxh64.d.ts +41 -0
  28. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/sha/index.browser.d.ts +2 -0
  29. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/sha/index.d.ts +3 -0
  30. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/sha/sha512/Sha512.d.ts +12 -0
  31. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/sha/sha512/Sha512Js.d.ts +22 -0
  32. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/sha/sha512/Sha512WebCrypto.d.ts +14 -0
  33. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/U64.d.ts +27 -0
  34. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/Xxh3.d.ts +65 -0
  35. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/index.d.ts +3 -0
  36. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/xxhash3_128/Xxh3_128.d.ts +12 -0
  37. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/xxhash3_64/Xxh3_64.d.ts +10 -0
  38. package/node_modules/@aws-sdk/checksums/dist-types/ts3.4/submodules/xxh/xxhash64/Xxh64.d.ts +17 -0
  39. package/node_modules/@aws-sdk/checksums/package.json +13 -3
  40. package/node_modules/@aws-sdk/checksums/xxh.d.ts +5 -0
  41. package/node_modules/@aws-sdk/checksums/xxh.js +5 -0
  42. package/node_modules/@aws-sdk/client-cloudwatch-logs/dist-cjs/index.js +1 -1
  43. package/node_modules/@aws-sdk/client-cloudwatch-logs/package.json +3 -3
  44. package/node_modules/@aws-sdk/client-route-53/dist-cjs/index.js +1 -1
  45. package/node_modules/@aws-sdk/client-route-53/package.json +3 -3
  46. package/node_modules/@aws-sdk/client-s3/dist-cjs/index.js +1 -1
  47. package/node_modules/@aws-sdk/client-s3/package.json +6 -6
  48. package/node_modules/@aws-sdk/client-ses/dist-cjs/index.js +1 -1
  49. package/node_modules/@aws-sdk/client-ses/package.json +3 -3
  50. package/node_modules/@aws-sdk/client-ssm/dist-cjs/index.js +1 -1
  51. package/node_modules/@aws-sdk/client-ssm/package.json +3 -3
  52. package/node_modules/@aws-sdk/core/dist-cjs/submodules/protocols/index.js +51 -1
  53. package/node_modules/@aws-sdk/core/dist-es/submodules/protocols/index.js +1 -0
  54. package/node_modules/@aws-sdk/core/dist-es/submodules/protocols/json/AwsSmithyRpcV2JsonProtocol.js +51 -0
  55. package/node_modules/@aws-sdk/core/dist-types/submodules/protocols/index.d.ts +1 -0
  56. package/node_modules/@aws-sdk/core/dist-types/submodules/protocols/json/AwsSmithyRpcV2JsonProtocol.d.ts +25 -0
  57. package/node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/index.d.ts +1 -0
  58. package/node_modules/@aws-sdk/core/dist-types/ts3.4/submodules/protocols/json/AwsSmithyRpcV2JsonProtocol.d.ts +36 -0
  59. package/node_modules/@aws-sdk/core/package.json +1 -1
  60. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  61. package/node_modules/@aws-sdk/credential-provider-http/package.json +2 -2
  62. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  63. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  64. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  65. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  66. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  67. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  68. package/node_modules/@aws-sdk/middleware-sdk-s3/package.json +2 -2
  69. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/cognito-identity/index.js +1 -1
  70. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/signin/index.js +1 -1
  71. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso/index.js +1 -1
  72. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sso-oidc/index.js +1 -1
  73. package/node_modules/@aws-sdk/nested-clients/dist-cjs/submodules/sts/index.js +1 -1
  74. package/node_modules/@aws-sdk/nested-clients/package.json +2 -2
  75. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  76. package/node_modules/mailparser/CHANGELOG.md +26 -0
  77. package/node_modules/mailparser/README.md +21 -1
  78. package/node_modules/mailparser/dist/cjs/index.d.ts +3 -1
  79. package/node_modules/mailparser/dist/cjs/index.js +4 -2
  80. package/node_modules/mailparser/dist/cjs/mail-parser.d.ts +8 -1
  81. package/node_modules/mailparser/dist/cjs/mail-parser.js +5 -0
  82. package/node_modules/mailparser/dist/cjs/otp-token.d.ts +9 -0
  83. package/node_modules/mailparser/dist/cjs/otp-token.js +186 -0
  84. package/node_modules/mailparser/dist/cjs/simple-parser.js +3 -0
  85. package/node_modules/mailparser/dist/cjs/types.d.ts +21 -0
  86. package/node_modules/mailparser/dist/esm/index.d.ts +3 -1
  87. package/node_modules/mailparser/dist/esm/index.js +3 -2
  88. package/node_modules/mailparser/dist/esm/mail-parser.d.ts +8 -1
  89. package/node_modules/mailparser/dist/esm/mail-parser.js +5 -0
  90. package/node_modules/mailparser/dist/esm/otp-token.d.ts +9 -0
  91. package/node_modules/mailparser/dist/esm/otp-token.js +180 -0
  92. package/node_modules/mailparser/dist/esm/simple-parser.js +3 -0
  93. package/node_modules/mailparser/dist/esm/types.d.ts +21 -0
  94. package/node_modules/mailparser/package.json +3 -3
  95. package/node_modules/nodemailer/CHANGELOG.md +19 -0
  96. package/node_modules/nodemailer/dist/cjs/dkim/sign.js +1 -1
  97. package/node_modules/nodemailer/dist/cjs/errors.d.ts +2 -0
  98. package/node_modules/nodemailer/dist/cjs/errors.js +3 -1
  99. package/node_modules/nodemailer/dist/cjs/mail-composer/index.d.ts +12 -0
  100. package/node_modules/nodemailer/dist/cjs/mail-composer/index.js +53 -0
  101. package/node_modules/nodemailer/dist/cjs/mailer/index.d.ts +3 -2
  102. package/node_modules/nodemailer/dist/cjs/mime-node/index.js +10 -2
  103. package/node_modules/nodemailer/dist/cjs/nodemailer.d.ts +1 -1
  104. package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
  105. package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
  106. package/node_modules/nodemailer/dist/cjs/well-known/services.js +7 -0
  107. package/node_modules/nodemailer/dist/esm/dkim/sign.js +1 -1
  108. package/node_modules/nodemailer/dist/esm/errors.d.ts +2 -0
  109. package/node_modules/nodemailer/dist/esm/errors.js +2 -0
  110. package/node_modules/nodemailer/dist/esm/mail-composer/index.d.ts +12 -0
  111. package/node_modules/nodemailer/dist/esm/mail-composer/index.js +53 -0
  112. package/node_modules/nodemailer/dist/esm/mailer/index.d.ts +3 -2
  113. package/node_modules/nodemailer/dist/esm/mime-node/index.js +10 -2
  114. package/node_modules/nodemailer/dist/esm/nodemailer.d.ts +1 -1
  115. package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
  116. package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
  117. package/node_modules/nodemailer/dist/esm/well-known/services.js +7 -0
  118. package/node_modules/nodemailer/dist/well-known/services.json +8 -0
  119. package/node_modules/nodemailer/package.json +5 -5
  120. package/package.json +7 -7
@@ -1,12 +1,14 @@
1
1
  import MailParser from './mail-parser.js';
2
2
  import simpleParser from './simple-parser.js';
3
3
  import { parseAddressHeader } from './address-header.js';
4
- export { MailParser, simpleParser, parseAddressHeader };
4
+ import { parseOtpToken } from './otp-token.js';
5
+ export { MailParser, simpleParser, parseAddressHeader, parseOtpToken };
5
6
  export type { SimpleParserCallback } from './simple-parser.js';
6
7
  export type * from './types.js';
7
8
  declare const _default: {
8
9
  MailParser: typeof MailParser;
9
10
  simpleParser: typeof simpleParser;
10
11
  parseAddressHeader: typeof parseAddressHeader;
12
+ parseOtpToken: typeof parseOtpToken;
11
13
  };
12
14
  export default _default;
@@ -1,5 +1,6 @@
1
1
  import MailParser from './mail-parser.js';
2
2
  import simpleParser from './simple-parser.js';
3
3
  import { parseAddressHeader } from './address-header.js';
4
- export { MailParser, simpleParser, parseAddressHeader };
5
- export default { MailParser, simpleParser, parseAddressHeader };
4
+ import { parseOtpToken } from './otp-token.js';
5
+ export { MailParser, simpleParser, parseAddressHeader, parseOtpToken };
6
+ export default { MailParser, simpleParser, parseAddressHeader, parseOtpToken };
@@ -1,6 +1,6 @@
1
1
  import { Transform } from 'node:stream';
2
2
  import type { TransformCallback } from 'node:stream';
3
- import type { AddressObject, AttachmentStream, EmailAddress, HeaderLines, Headers, MailParserOptions, MessageText } from './types.js';
3
+ import type { AddressObject, AttachmentStream, EmailAddress, HeaderLines, Headers, MailParserOptions, MessageText, Otp } from './types.js';
4
4
  /**
5
5
  * A lower level, streaming e-mail parser. It is a Transform stream: write the raw message in and
6
6
  * read the parsed content out. It emits:
@@ -46,6 +46,13 @@ declare class MailParser extends Transform {
46
46
  inReplyTo?: string;
47
47
  /** The `Reply-To` header, set once the headers have been parsed. */
48
48
  replyTo?: AddressObject;
49
+ /**
50
+ * One-time passcode from the `OTP-Token` header, set once the headers have been parsed if the
51
+ * message has a valid one. The header is not authenticated, see `ParsedMail.otp`.
52
+ *
53
+ * @experimental
54
+ */
55
+ otp?: Otp;
49
56
  constructor(config?: MailParserOptions);
50
57
  _transform(chunk: Buffer, encoding: BufferEncoding, done: TransformCallback): void;
51
58
  _flush(done: TransformCallback): void;
@@ -9,6 +9,7 @@ import { Transform } from 'node:stream';
9
9
  import tlds from './tlds.cjs';
10
10
  import StreamHash from './stream-hash.js';
11
11
  import { decodeAddresses, formatAddressesHTML, formatAddressesText } from './address-header.js';
12
+ import { getOtp } from './otp-token.js';
12
13
  const { Splitter, ChunkedPassthrough } = mailsplit;
13
14
  // Bounds for link detection in text bodies. Message content is untrusted and
14
15
  // linkify-it backtracks heavily on crafted input: matching a host is quadratic
@@ -858,6 +859,10 @@ class MailParser extends Transform {
858
859
  this[key.replace(/-([a-z])/g, (m, c) => c.toUpperCase())] = node.headers.get(key);
859
860
  }
860
861
  });
862
+ let otp = getOtp(node.headerLines);
863
+ if (otp) {
864
+ this.otp = otp;
865
+ }
861
866
  this.emit('headers', node.headers);
862
867
  if (node.headerLines) {
863
868
  this.emit('headerLines', node.headerLines);
@@ -0,0 +1,9 @@
1
+ import type { Otp } from './types.js';
2
+ /**
3
+ * Returns the one-time passcode from the values of the `OTP-Token` headers of a message header
4
+ * section, following the receiver steps of the draft (section 3.2), or undefined if there is no
5
+ * valid one. Pass every `OTP-Token` value of the message's own header section, not of a MIME
6
+ * part: more than one makes the message have none. The values are the raw ones, with any
7
+ * folding left in and no encoded words decoded
8
+ */
9
+ export declare function parseOtpToken(values: string | string[] | undefined): Otp | undefined;
@@ -0,0 +1,180 @@
1
+ // Parser for the OTP-Token header of draft-goto-otp-token-01, an Internet-Draft that expires
2
+ // on 25 March 2027. By then check for a newer revision of the draft or a published RFC, the
3
+ // header name and its parameters are still open questions in the draft.
4
+ //
5
+ // The header value is a structured field item (RFC 9651): a String holding the passcode,
6
+ // with an `origin` parameter naming the origin the passcode is bound to. Unknown parameters
7
+ // are ignored, but they still have to be valid, otherwise the whole field fails to parse.
8
+ // RFC 9651 lets a parser limit the size of a field. A passcode and an origin take well under
9
+ // a kilobyte, the limit keeps the regular expressions below away from megabyte inputs
10
+ const MAX_FIELD_LENGTH = 8192;
11
+ // The bare item syntax of RFC 9651 4.2.3.1, keyed by the first character of the item. An
12
+ // Integer has at most 15 digits, a Decimal at most 12 before and 1 to 3 after the dot, and a
13
+ // Date is an Integer. Whatever follows a match is left for the caller, which only accepts a
14
+ // parameter separator or the end of the field, so a 16th digit or a 4th decimal fails there
15
+ const NUMBER = /-?(?:\d{1,12}\.\d{1,3}|\d{1,15})/y;
16
+ const STRING = /"((?:[\x20\x21\x23-\x5b\x5d-\x7e]|\\["\\])*)"/y;
17
+ const TOKEN = /[A-Za-z*][!#$%&'*+\-.^_`|~0-9A-Za-z:/]*/y;
18
+ const BYTE_SEQUENCE = /:[A-Za-z0-9+/=]*:/y;
19
+ const BOOLEAN = /\?[01]/y;
20
+ const DATE = /@-?\d{1,15}/y;
21
+ const DISPLAY_STRING = /%"((?:[\x20\x21\x23\x24\x26-\x7e]|%[0-9a-f]{2})*)"/y;
22
+ const KEY = /[a-z*][a-z0-9_\-.*]*/y;
23
+ const SPACES = / */y;
24
+ // Matches the sticky expression at the cursor and moves past it, returns null if it does not match
25
+ function match(cursor, re) {
26
+ re.lastIndex = cursor.pos;
27
+ const result = re.exec(cursor.input);
28
+ if (result) {
29
+ cursor.pos = re.lastIndex;
30
+ }
31
+ return result;
32
+ }
33
+ // RFC 9651 4.2.5, returns the unescaped String or null if there is no valid String at the cursor
34
+ function parseString(cursor) {
35
+ const result = match(cursor, STRING);
36
+ return result && result[1].replace(/\\(["\\])/g, '$1');
37
+ }
38
+ // RFC 9651 4.2.10. The percent encoded bytes must be valid UTF-8, which decodeURIComponent
39
+ // checks as it decodes them
40
+ function isDisplayString(cursor) {
41
+ const result = match(cursor, DISPLAY_STRING);
42
+ if (!result) {
43
+ return false;
44
+ }
45
+ try {
46
+ decodeURIComponent(result[1]);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ // RFC 9651 4.2.3.1. Returns the value of a String, true for any other valid bare item and
54
+ // false for an invalid one
55
+ function parseBareItem(cursor) {
56
+ const c = cursor.input.charAt(cursor.pos);
57
+ switch (c) {
58
+ case '"':
59
+ return parseString(cursor) ?? false;
60
+ case ':':
61
+ return !!match(cursor, BYTE_SEQUENCE);
62
+ case '?':
63
+ return !!match(cursor, BOOLEAN);
64
+ case '@':
65
+ return !!match(cursor, DATE);
66
+ case '%':
67
+ return isDisplayString(cursor);
68
+ }
69
+ return !!match(cursor, c === '-' || (c >= '0' && c <= '9') ? NUMBER : TOKEN);
70
+ }
71
+ /**
72
+ * Parses a structured field item (RFC 9651 4.2 with field_type "item") whose bare item is a
73
+ * String. Returns the String and the String parameters, or null if the value does not parse
74
+ * or the bare item is of another type. A parameter of any other type is stored as true
75
+ * @internal
76
+ */
77
+ export function parseStringItem(value) {
78
+ const cursor = { input: value, pos: 0 };
79
+ match(cursor, SPACES);
80
+ const item = parseString(cursor);
81
+ if (item === null) {
82
+ return null;
83
+ }
84
+ // a later parameter with the same key replaces the earlier one
85
+ const params = new Map();
86
+ while (cursor.input[cursor.pos] === ';') {
87
+ cursor.pos++;
88
+ match(cursor, SPACES);
89
+ const key = match(cursor, KEY);
90
+ if (!key) {
91
+ return null;
92
+ }
93
+ let paramValue = true;
94
+ if (cursor.input[cursor.pos] === '=') {
95
+ cursor.pos++;
96
+ paramValue = parseBareItem(cursor);
97
+ if (paramValue === false) {
98
+ return null;
99
+ }
100
+ }
101
+ params.set(key[0], paramValue);
102
+ }
103
+ match(cursor, SPACES);
104
+ return cursor.pos === cursor.input.length ? { value: item, params } : null;
105
+ }
106
+ /**
107
+ * Checks that the origin is the canonical ASCII serialization of a potentially trustworthy
108
+ * origin, as the draft requires: https, or http on a loopback host
109
+ * @internal
110
+ */
111
+ export function isOtpOrigin(origin) {
112
+ let url;
113
+ try {
114
+ url = new URL(origin);
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ // the URL parser lowercases the host, converts it to punycode and drops the default port, so
120
+ // a value that is not canonical, or has a path, userinfo or an opaque origin, does not match
121
+ // its own origin. The WHATWG parser lets characters such as a quote or a brace into a host,
122
+ // which the origin grammar (RFC 6454) does not allow, so only a DNS name, an IPv4 address or a
123
+ // bracketed IPv6 address is accepted
124
+ if (url.origin !== origin || !/^([a-z0-9_-]+\.)*[a-z0-9_-]+$|^\[[0-9a-f:.]+\]$/.test(url.hostname)) {
125
+ return false;
126
+ }
127
+ const isLoopback = /^(localhost|.+\.localhost|127(\.\d+){3}|\[::1\])$/.test(url.hostname);
128
+ return url.protocol === 'https:' || (url.protocol === 'http:' && isLoopback);
129
+ }
130
+ // The field name with its colon. A line with whitespace before the colon is not counted, it
131
+ // is obsolete syntax (RFC 5322 4.5) the draft has no reason to accept
132
+ const OTP_HEADER = /^otp-token:/i;
133
+ /**
134
+ * Returns the value of a header without its line breaks and the whitespace around it, or null if
135
+ * the value is longer than the field size limit
136
+ */
137
+ function getFieldValue(value) {
138
+ // unfolding removes the line breaks and keeps the whitespace that follows them (RFC 5322 2.2.3)
139
+ value = value.replace(/\r?\n/g, '');
140
+ let start = 0;
141
+ let end = value.length;
142
+ while (start < end && (value[start] === ' ' || value[start] === '\t')) {
143
+ start++;
144
+ }
145
+ while (end > start && (value[end - 1] === ' ' || value[end - 1] === '\t')) {
146
+ end--;
147
+ }
148
+ return end - start > MAX_FIELD_LENGTH ? null : value.slice(start, end);
149
+ }
150
+ /**
151
+ * Returns the one-time passcode from the values of the `OTP-Token` headers of a message header
152
+ * section, following the receiver steps of the draft (section 3.2), or undefined if there is no
153
+ * valid one. Pass every `OTP-Token` value of the message's own header section, not of a MIME
154
+ * part: more than one makes the message have none. The values are the raw ones, with any
155
+ * folding left in and no encoded words decoded
156
+ */
157
+ export function parseOtpToken(values) {
158
+ const list = typeof values === 'string' ? [values] : values;
159
+ if (!Array.isArray(list) || list.length !== 1 || typeof list[0] !== 'string') {
160
+ return undefined;
161
+ }
162
+ const value = getFieldValue(list[0]);
163
+ const parsed = value === null ? null : parseStringItem(value);
164
+ const origin = parsed?.params.get('origin');
165
+ // an empty passcode is valid syntax, but there is nothing in it to fill in
166
+ if (!parsed?.value || typeof origin !== 'string' || !isOtpOrigin(origin)) {
167
+ return undefined;
168
+ }
169
+ return { code: parsed.value, origin };
170
+ }
171
+ /**
172
+ * Returns the one-time passcode from the OTP-Token header of a message, or undefined if the
173
+ * message has no valid one. Only the header of the message itself counts, the draft has an
174
+ * OTP-Token in a MIME part ignored
175
+ * @internal
176
+ */
177
+ export function getOtp(headerLines) {
178
+ const values = headerLines.filter(({ line }) => OTP_HEADER.test(line)).map(({ line }) => line.slice(line.indexOf(':') + 1));
179
+ return values.length ? parseOtpToken(values) : undefined;
180
+ }
@@ -46,6 +46,9 @@ function simpleParser(input, options, callback) {
46
46
  parser.on('headers', headers => {
47
47
  mail.headers = headers;
48
48
  mail.headerLines = parser.headerLines;
49
+ if (parser.otp) {
50
+ mail.otp = parser.otp;
51
+ }
49
52
  });
50
53
  let reading = false;
51
54
  let reader = () => {
@@ -104,6 +104,17 @@ export type HeaderLines = ReadonlyArray<{
104
104
  key: string;
105
105
  line: string;
106
106
  }>;
107
+ /**
108
+ * A one-time passcode from the `OTP-Token` header (draft-goto-otp-token), so that a mail
109
+ * client can offer it for the origin it is bound to. The header is set by the sender and not
110
+ * authenticated by the parser, check the DKIM signature of the message before trusting it.
111
+ */
112
+ export interface Otp {
113
+ /** The passcode, printable ASCII. */
114
+ code: string;
115
+ /** Canonical ASCII serialization of the origin the code is bound to, eg. `https://example.com`. */
116
+ origin: string;
117
+ }
107
118
  /**
108
119
  * The properties every attachment has, whether streamed by {@link MailParser} or buffered by
109
120
  * `simpleParser`.
@@ -329,6 +340,16 @@ export interface ParsedMail {
329
340
  inReplyTo?: string;
330
341
  /** The `Reply-To` header. */
331
342
  replyTo?: AddressObject;
343
+ /**
344
+ * One-time passcode from the `OTP-Token` header of the message. Set only when the header
345
+ * section has exactly one `OTP-Token` header and it is valid. The header is not
346
+ * authenticated, verify that the message is signed by the domain of the origin before
347
+ * trusting it.
348
+ *
349
+ * @experimental The header is defined by an Internet-Draft, so this property may change
350
+ * in a minor release when the draft does.
351
+ */
352
+ otp?: Otp;
332
353
  }
333
354
  /**
334
355
  * A message to parse: the raw message as a Buffer or a string, or a readable stream of it.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mailparser",
3
- "version": "3.12.4",
3
+ "version": "3.14.1",
4
4
  "description": "Parse e-mails",
5
5
  "type": "module",
6
6
  "main": "./dist/cjs/index.js",
@@ -97,7 +97,7 @@
97
97
  "html-to-text": "10.0.1",
98
98
  "libmime": "5.6.3",
99
99
  "linkify-it": "5.0.2",
100
- "nodemailer": "10.2.1",
100
+ "nodemailer": "10.3.1",
101
101
  "punycode.js": "2.3.1",
102
102
  "tlds": "1.261.0"
103
103
  },
@@ -112,7 +112,7 @@
112
112
  "prettier": "3.9.10",
113
113
  "tsx": "4.23.15",
114
114
  "typescript": "6.0.3",
115
- "typescript-eslint": "8.72.0"
115
+ "typescript-eslint": "8.72.1"
116
116
  },
117
117
  "repository": {
118
118
  "type": "git",
@@ -1,5 +1,24 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [10.3.1](https://github.com/nodemailer/nodemailer/compare/v10.3.0...v10.3.1) (2026-10-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * apply the otp option in MailComposer too ([1d4087f](https://github.com/nodemailer/nodemailer/commit/1d4087f358d124267bddd5eb5e11a4e22fccd3d0))
9
+
10
+ ## [10.3.0](https://github.com/nodemailer/nodemailer/compare/v10.2.1...v10.3.0) (2026-10-29)
11
+
12
+
13
+ ### Features
14
+
15
+ * add the otp option for the OTP-Token header ([7789e15](https://github.com/nodemailer/nodemailer/commit/7789e1501e4f555e9754a15664aa30f1415902b9))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * encode a Content-ID header as a message id ([#1904](https://github.com/nodemailer/nodemailer/issues/1904)) ([47f2b25](https://github.com/nodemailer/nodemailer/commit/47f2b252fccc8ec6a1a5700e12c1087ae3633e17))
21
+
3
22
  ## [10.2.1](https://github.com/nodemailer/nodemailer/compare/v10.2.0...v10.2.1) (2026-10-22)
4
23
 
5
24
 
@@ -68,7 +68,7 @@ function sign(headers, hashAlgo, bodyHash, options) {
68
68
  'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
69
69
  'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
70
70
  'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
71
- 'List-Owner:List-Archive';
71
+ 'List-Owner:List-Archive:OTP-Token';
72
72
  const fieldNames = options.headerFieldNames || defaultFieldNames;
73
73
  const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
74
74
  const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
@@ -21,6 +21,7 @@ export declare const ERROR_CODES: {
21
21
  readonly EPROTOCOL: "Invalid SMTP server response";
22
22
  readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
23
23
  readonly EMESSAGE: "Message delivery error";
24
+ readonly EOTP: "Invalid one-time passcode data (otp option)";
24
25
  readonly ESTREAM: "Stream processing error";
25
26
  readonly EAUTH: "Authentication failed";
26
27
  readonly ENOAUTH: "Authentication credentials not provided";
@@ -48,6 +49,7 @@ export declare const EREQUIRETLS = "EREQUIRETLS";
48
49
  export declare const EPROTOCOL = "EPROTOCOL";
49
50
  export declare const EENVELOPE = "EENVELOPE";
50
51
  export declare const EMESSAGE = "EMESSAGE";
52
+ export declare const EOTP = "EOTP";
51
53
  export declare const ESTREAM = "ESTREAM";
52
54
  export declare const EAUTH = "EAUTH";
53
55
  export declare const ENOAUTH = "ENOAUTH";
@@ -10,7 +10,7 @@
10
10
  * err.code = errors.ECONNECTION;
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.ERR_ACCESS_DENIED = exports.isPermanentResponseCode = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
13
+ exports.ERR_ACCESS_DENIED = exports.isPermanentResponseCode = exports.isTransientError = exports.EFETCH = exports.EURLACCESS = exports.EFILEACCESS = exports.EPROXY = exports.ECONFIG = exports.ESES = exports.ESENDMAIL = exports.EMAXRECIPIENTS = exports.EMAXLIMIT = exports.EOAUTH2 = exports.ENOAUTH = exports.EAUTH = exports.ESTREAM = exports.EOTP = exports.EMESSAGE = exports.EENVELOPE = exports.EPROTOCOL = exports.EREQUIRETLS = exports.ETLS = exports.EDNS = exports.ESOCKET = exports.ETIMEDOUT = exports.ECONNECTION = exports.ERROR_CODES = void 0;
14
14
  /**
15
15
  * Error code descriptions for documentation and debugging
16
16
  */
@@ -27,6 +27,7 @@ exports.ERROR_CODES = {
27
27
  EPROTOCOL: 'Invalid SMTP server response',
28
28
  EENVELOPE: 'Invalid mail envelope (sender or recipients)',
29
29
  EMESSAGE: 'Message delivery error',
30
+ EOTP: 'Invalid one-time passcode data (otp option)',
30
31
  ESTREAM: 'Stream processing error',
31
32
  // Authentication errors
32
33
  EAUTH: 'Authentication failed',
@@ -55,6 +56,7 @@ exports.EREQUIRETLS = 'EREQUIRETLS';
55
56
  exports.EPROTOCOL = 'EPROTOCOL';
56
57
  exports.EENVELOPE = 'EENVELOPE';
57
58
  exports.EMESSAGE = 'EMESSAGE';
59
+ exports.EOTP = 'EOTP';
58
60
  exports.ESTREAM = 'ESTREAM';
59
61
  exports.EAUTH = 'EAUTH';
60
62
  exports.ENOAUTH = 'ENOAUTH';
@@ -72,6 +72,16 @@ export type MailComposerListHeaderEntry = string | {
72
72
  export interface MailComposerListHeaders {
73
73
  [key: string]: MailComposerListHeaderEntry | (MailComposerListHeaderEntry | MailComposerListHeaderEntry[])[];
74
74
  }
75
+ /**
76
+ * The otp value: a one-time passcode and the origin it is bound to, sent as the OTP-Token
77
+ * header (draft-goto-otp-token)
78
+ */
79
+ export interface MailComposerOtp {
80
+ /** The passcode, printable ascii */
81
+ code: string;
82
+ /** Origin the code is bound to, https or a loopback http origin. A full url is reduced to its origin */
83
+ origin: string;
84
+ }
75
85
  /**
76
86
  * Encoding for the non-ascii header values: quoted-printable ('Q', the default) or base64
77
87
  * ('B'). Only the first letter is read, so the short forms work as well
@@ -114,6 +124,8 @@ export interface MailComposerOptions {
114
124
  headers?: MimeNodeHeaders | undefined;
115
125
  /** List-* headers, read by the mailer */
116
126
  list?: MailComposerListHeaders | undefined;
127
+ /** One-time passcode for the OTP-Token header. An invalid value makes compile() throw an EOTP error */
128
+ otp?: MailComposerOtp | undefined;
117
129
  /** SMTP envelope to use instead of the one generated from the headers */
118
130
  envelope?: MimeNodeEnvelopeInput | undefined;
119
131
  /** Content-Transfer-Encoding to force for the text/* nodes that do not set their own */
@@ -39,6 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const index_js_1 = __importDefault(require("../mime-node/index.js"));
41
41
  const mimeFuncs = __importStar(require("../mime-funcs/index.js"));
42
+ const errors = __importStar(require("../errors.js"));
42
43
  const index_js_2 = require("../shared/index.js");
43
44
  /**
44
45
  * Creates the object for composing a MimeNode instance out from the mail options
@@ -133,6 +134,10 @@ class MailComposer {
133
134
  if (this.mail.headers) {
134
135
  this.message.addHeader(this.mail.headers);
135
136
  }
137
+ // after the custom headers, so it replaces an OTP-Token from them, the field may appear only once
138
+ if (this.mail.otp) {
139
+ this.message.setHeader('OTP-Token', formatOtpToken(this.mail.otp));
140
+ }
136
141
  // Add headers to the root node, always overrides custom headers
137
142
  ['from', 'sender', 'to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'message-id', 'date'].forEach(header => {
138
143
  const key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
@@ -641,6 +646,54 @@ class MailComposer {
641
646
  return element;
642
647
  }
643
648
  }
649
+ /**
650
+ * Returns the value of the OTP-Token header for the otp option. The header is defined by
651
+ * draft-goto-otp-token-01, an Internet-Draft that expires on 25 March 2027. By then check
652
+ * for a newer revision of the draft or a published RFC, the header name and its
653
+ * parameters are still open questions in the draft.
654
+ */
655
+ function formatOtpToken(otp) {
656
+ // a structured field String can only hold printable ascii. The code is a secret the recipient
657
+ // compares against the one in the message body, so it is rejected rather than cleaned up
658
+ if (typeof otp.code !== 'string' || !/^[\x20-\x7e]+$/.test(otp.code)) {
659
+ throw otpError('The otp code must be a non-empty string of printable ASCII characters');
660
+ }
661
+ // a structured field String escapes the same two characters as a quoted-string
662
+ return mimeFuncs.quoteString(otp.code) + '; origin=' + mimeFuncs.quoteString(getOtpOrigin(otp.origin));
663
+ }
664
+ /**
665
+ * Returns the canonical ascii serialization of the origin the code is bound to. The draft
666
+ * requires a potentially trustworthy origin, so only https is accepted, plus http on a
667
+ * loopback host for local development
668
+ */
669
+ function getOtpOrigin(origin) {
670
+ let url;
671
+ try {
672
+ url = new URL(String(origin));
673
+ }
674
+ catch {
675
+ // handled below
676
+ }
677
+ // the WHATWG parser lets characters such as a quote or a brace into a host, which the
678
+ // origin grammar the draft requires (RFC 6454) does not allow, so a receiver would discard
679
+ // the header. Only a DNS name, an IPv4 address or a bracketed IPv6 address is accepted.
680
+ // Userinfo is refused as well, an origin never has it and it turns a value built as
681
+ // 'https://example.com' + input into the origin of whatever host follows an "@"
682
+ if (!url || url.username || url.password || !/^([a-z0-9_-]+\.)*[a-z0-9_-]+$|^\[[0-9a-f:.]+\]$/.test(url.hostname)) {
683
+ throw otpError('The otp origin must be an absolute https URL');
684
+ }
685
+ const isLoopback = /^(localhost|.+\.localhost|127(\.\d+){3}|\[::1\])$/.test(url.hostname);
686
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopback)) {
687
+ throw otpError('The otp origin must be an https URL, or an http URL on a loopback host');
688
+ }
689
+ // the URL parser already lowercases the host, converts it to punycode and drops the default port
690
+ return url.origin;
691
+ }
692
+ function otpError(message) {
693
+ const err = new Error(message);
694
+ err.code = errors.EOTP;
695
+ return err;
696
+ }
644
697
  exports.default = MailComposer;
645
698
  module.exports = exports.default;
646
699
  Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
@@ -4,7 +4,7 @@ import DKIM, { type DKIMOptions } from '../dkim/index.js';
4
4
  import MailMessage, { type MailDefaults, type SendMailOptions } from './mail-message.js';
5
5
  import net from 'node:net';
6
6
  import type { ConnectionOptions } from 'node:tls';
7
- import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerTextEncoding } from '../mail-composer/index.js';
7
+ import type { MailComposerAlternative, MailComposerAttachment, MailComposerIcalEvent, MailComposerListHeaderEntry, MailComposerListHeaders, MailComposerOtp, MailComposerTextEncoding } from '../mail-composer/index.js';
8
8
  import type { Callback, NodemailerError, ResultCallback } from '../errors.js';
9
9
  import type { ParsedUrl } from '../shared/url.js';
10
10
  import type { MimeNodeAddress, MimeNodeEnvelope, MimeNodeEnvelopeInput, MimeNodeHeaders, MimeNodeOptions } from '../mime-node/index.js';
@@ -227,11 +227,12 @@ declare namespace Mail {
227
227
  type Headers = MimeNodeHeaders;
228
228
  type ListHeader = MailComposerListHeaderEntry;
229
229
  type ListHeaders = MailComposerListHeaders;
230
+ type Otp = MailComposerOtp;
230
231
  type Envelope = MimeNodeEnvelopeInput;
231
232
  type Connection = SocketOptions;
232
233
  type TextEncoding = MailComposerTextEncoding;
233
234
  type PluginFunction<T = SentMessageInfo> = MailPluginFunction<T>;
234
235
  }
235
236
  /** The same aliases as module level exports, for `import * as Mail` and `import Mail = require()` */
236
- export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
237
+ export type { SendMailOptions as Options, MimeNodeAddress as Address, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerAlternative as AmpAttachment, MailComposerIcalEvent as IcalAttachment, MimeNodeHeaders as Headers, MailComposerListHeaderEntry as ListHeader, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp, MimeNodeEnvelopeInput as Envelope, SocketOptions as Connection, MailComposerTextEncoding as TextEncoding };
237
238
  export default Mail;
@@ -1302,7 +1302,7 @@ class MimeNode {
1302
1302
  .replace(/[\s\x00-\x20\x7f:]/g, '')
1303
1303
  .toLowerCase()
1304
1304
  // use uppercase words, except MIME
1305
- .replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
1305
+ .replace(/^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^OTP(?=-Token$)|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, c => c.toUpperCase())
1306
1306
  // special case
1307
1307
  .replace(/^Content-Features$/i, 'Content-features');
1308
1308
  return key;
@@ -1374,7 +1374,7 @@ class MimeNode {
1374
1374
  // values enclosed in <>
1375
1375
  case 'Message-ID':
1376
1376
  case 'In-Reply-To':
1377
- case 'Content-Id':
1377
+ case 'Content-ID':
1378
1378
  // a msg-id is structured, so an encoded word inside the angle brackets would
1379
1379
  // be read as literal text. drop the characters that can not appear in a header
1380
1380
  // at all, but leave HT alone, it separates the ids of a multi id value
@@ -1389,6 +1389,14 @@ class MimeNode {
1389
1389
  value = value + '>';
1390
1390
  }
1391
1391
  return value;
1392
+ // a structured field (RFC 9651) that can not be mime encoded, an encoded word would
1393
+ // turn its quoted strings into text that no structured field parser reads. Only
1394
+ // the characters that can not appear in a header at all are removed
1395
+ case 'OTP-Token':
1396
+ return (value || '')
1397
+ .toString()
1398
+ .replace(/\r?\n|\r/g, ' ')
1399
+ .replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, '');
1392
1400
  // space separated list of values enclosed in <>
1393
1401
  case 'References': {
1394
1402
  const ids = [];
@@ -111,7 +111,7 @@ export type { Mail, MailDefaults, SentMessageInfo, Transport, TransportOptions }
111
111
  export type { SendMailOptions, Transporter } from './mailer/index.js';
112
112
  export type { NodemailerError, ErrorCode } from './errors.js';
113
113
  export type { MailMessage, MailMessageData, PluginFunction, VerifyCallback } from './mailer/index.js';
114
- export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders } from './mail-composer/index.js';
114
+ export type { MailComposerOptions, MailComposerAttachment as Attachment, MailComposerAlternative as AttachmentLike, MailComposerIcalEvent as IcalAttachment, MailComposerListHeaders as ListHeaders, MailComposerOtp as Otp } from './mail-composer/index.js';
115
115
  export type { MimeNodeAddress as Address, MimeNodeHeaders as Headers } from './mime-node/index.js';
116
116
  export type { SMTPConnectionOptions, SMTPConnectionAuth, SMTPEnvelope, SMTPEnvelopeDsn } from './smtp-connection/index.js';
117
117
  export type { DKIMOptions } from './dkim/index.js';
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.2.1";
2
+ export declare const version = "10.3.1";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -3,5 +3,5 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
5
  exports.name = 'nodemailer';
6
- exports.version = '10.2.1';
6
+ exports.version = '10.3.1';
7
7
  exports.homepage = 'https://nodemailer.com/';
@@ -208,6 +208,13 @@ exports.services = {
208
208
  "host": "smtp.europe.secureserver.net",
209
209
  "port": 25
210
210
  },
211
+ "Groundmail": {
212
+ "description": "Groundmail",
213
+ "host": "smtp.groundmail.dev",
214
+ "port": 587,
215
+ "secure": false,
216
+ "requireTLS": true
217
+ },
211
218
  "hot.ee": {
212
219
  "description": "Hot.ee (Estonian email provider)",
213
220
  "host": "mail.hot.ee"
@@ -30,7 +30,7 @@ function sign(headers, hashAlgo, bodyHash, options) {
30
30
  'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
31
31
  'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
32
32
  'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
33
- 'List-Owner:List-Archive';
33
+ 'List-Owner:List-Archive:OTP-Token';
34
34
  const fieldNames = options.headerFieldNames || defaultFieldNames;
35
35
  const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
36
36
  const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
@@ -21,6 +21,7 @@ export declare const ERROR_CODES: {
21
21
  readonly EPROTOCOL: "Invalid SMTP server response";
22
22
  readonly EENVELOPE: "Invalid mail envelope (sender or recipients)";
23
23
  readonly EMESSAGE: "Message delivery error";
24
+ readonly EOTP: "Invalid one-time passcode data (otp option)";
24
25
  readonly ESTREAM: "Stream processing error";
25
26
  readonly EAUTH: "Authentication failed";
26
27
  readonly ENOAUTH: "Authentication credentials not provided";
@@ -48,6 +49,7 @@ export declare const EREQUIRETLS = "EREQUIRETLS";
48
49
  export declare const EPROTOCOL = "EPROTOCOL";
49
50
  export declare const EENVELOPE = "EENVELOPE";
50
51
  export declare const EMESSAGE = "EMESSAGE";
52
+ export declare const EOTP = "EOTP";
51
53
  export declare const ESTREAM = "ESTREAM";
52
54
  export declare const EAUTH = "EAUTH";
53
55
  export declare const ENOAUTH = "ENOAUTH";