@push.rocks/smartmta 13.0.1 → 13.1.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.
package/changelog.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-17 - 13.1.0
4
+
5
+ ### Features
6
+
7
+ - The SMTP listener refuses a message that contains a bare CR or bare LF (RFC 5321 §2.3.8) with `550 5.6.0 Message contains bare CR or LF`. The message is still read to its real `<CRLF>.<CRLF>` end-of-data marker, so the session stays in sync (a bare LF never starts a line, so `<LF>.<CRLF>` cannot end the message early and smuggle the following lines in as commands); the transaction is reset and the session continues. No hook is called for such a message. Before, the listener accepted it and every relay path (`enqueueRawEmail()`, `sendRawOutboundEmail()`, `sendRawEmail()` signed or `unsignedRelay`) then refused the stored bytes as non-retryable, an accept-then-bounce. Senders that emit bare LF or bare CR now get 550 5.6.0 at the end of DATA instead of a later bounce; there is no option to accept or normalize them.
8
+
9
+ ### Fixes
10
+
11
+ - An oversized message is read to its end-of-data marker only up to twice `maxMessageSize`. Past that, without a marker, the listener answers `552 5.3.4 Message size exceeds maximum of <n> bytes; closing` and closes the connection, instead of reading an endless DATA stream until the data timeout. The reply is 552 rather than the 421 RFC 5321 §3.8 names for closing, so the client treats the message as permanently too large instead of retrying it.
12
+
13
+ ### Maintenance
14
+
15
+ - release tooling: `@git.zone/tsrust` ^5.0.0, `@git.zone/cli` ^10.0.0, `@types/node` ^26.6.5, pnpm 12.11.2.
16
+
3
17
  ## 2026-10-15 - 13.0.1
4
18
 
5
19
  ### Fixes
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "26d34a7674c6b5b739c8ad6764c4e7d5282534e0b577459f1eb7f2d6b73e5543",
3
+ "binarySha256": "d1e0606495226f9d36cb74853e0614ee900be2f210c50862828ed00f69b4c307",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartmta",
6
- "projectVersion": "13.0.1",
7
- "gitCommit": "e7bc2248fd9d2e90a3299bd05bfdabefa193f008",
6
+ "projectVersion": "13.1.0",
7
+ "gitCommit": "fd6770dc9cfb72b952b54cb20755d7daeb237ec1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-15T11:23:05.139Z",
10
- "tsrustVersion": "4.6.0",
9
+ "builtAt": "2026-10-17T01:14:37.136Z",
10
+ "tsrustVersion": "5.0.0",
11
11
  "binary": "mailer-bin",
12
12
  "target": "linux_amd64_musl"
13
13
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "0c3a0c4ad7a774b1797521e287aad257f2bf10a65bd8d2b7cdc35917a8692113",
3
+ "binarySha256": "3e4cbd3e7cd44945b166db2f976e14212e7758bff4c148d88b6441d60b4e6a8e",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartmta",
6
- "projectVersion": "13.0.1",
7
- "gitCommit": "e7bc2248fd9d2e90a3299bd05bfdabefa193f008",
6
+ "projectVersion": "13.1.0",
7
+ "gitCommit": "fd6770dc9cfb72b952b54cb20755d7daeb237ec1",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-15T11:25:18.459Z",
10
- "tsrustVersion": "4.6.0",
9
+ "builtAt": "2026-10-17T01:17:40.969Z",
10
+ "tsrustVersion": "5.0.0",
11
11
  "binary": "mailer-bin",
12
12
  "target": "linux_arm64_musl"
13
13
  }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmta',
6
- version: '13.0.1',
6
+ version: '13.1.0',
7
7
  description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHlIQUF5SDtDQUN2SSxDQUFBIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartmta",
3
- "version": "13.0.1",
3
+ "version": "13.1.0",
4
4
  "description": "A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.",
5
5
  "keywords": [
6
6
  "mta",
@@ -31,11 +31,11 @@
31
31
  ".": "./dist_ts/index.js"
32
32
  },
33
33
  "devDependencies": {
34
- "@git.zone/cli": "^9.1.0",
34
+ "@git.zone/cli": "^10.0.0",
35
35
  "@git.zone/tsbuild": "^6.0.2",
36
- "@git.zone/tsrust": "^4.6.0",
36
+ "@git.zone/tsrust": "^5.0.0",
37
37
  "@git.zone/tstest": "^8.0.0",
38
- "@types/node": "^26.6.4",
38
+ "@types/node": "^26.6.5",
39
39
  "tsx": "^4.23.15"
40
40
  },
41
41
  "dependencies": {
package/readme.md CHANGED
@@ -270,7 +270,7 @@ const emailServer = new UnifiedEmailServer(dcRouterRef, {
270
270
  storageManager: smartBucketBlobStorage,
271
271
  },
272
272
 
273
- maxMessageSize: 25 * 1024 * 1024, // 25 MB; a larger message is read to its end and refused with 552
273
+ maxMessageSize: 25 * 1024 * 1024, // 25 MB; a larger message is read to its end and refused with 552; past twice this size the connection is closed
274
274
  maxClients: 500,
275
275
  });
276
276
 
@@ -355,7 +355,7 @@ Without this, an authenticated submission addressed to a domain this server host
355
355
 
356
356
  `hooks.onMessageData` runs after `DATA` and before the final SMTP response — but only for envelopes with **no** resolved `store` or `deliver` action; terminal local deliveries go through `hooks.onAcceptEnvelope` instead and never invoke `onMessageData`. Its `rawMessage` holds the same exact received bytes as in `hooks.onAcceptEnvelope`, final CRLF included. Return `{ accepted: true }` only after your application has accepted responsibility for the message, such as after durable persistence. Set `continueProcessing: false` when the consumer owns all downstream processing.
357
357
 
358
- `hooks.onAcceptEnvelope` is the terminal durable owner for any envelope containing a resolved `store` or `deliver` action. It receives the exact raw RFC822 bytes (`rawMessage`): the message as the client sent it, with only the DATA dot-stuffing removed and the CRLF that ends the last line kept (RFC 5321 §4.1.1.4: the first CRLF of `<CRLF>.<CRLF>` belongs to the message), so the bytes can be relayed unchanged through `enqueueRawEmail()`, `sendRawOutboundEmail()` or an unsigned raw relay. It also receives the complete per-recipient route resolution (`resolvedRecipientRoutes`), and the Rust security verdicts (`securityResults`). It must durably persist the message before resolving — the SMTP `250` is only sent after the hook resolves. Throwing an `AcceptEnvelopeRejectionError` with a `5xx` code rejects the envelope permanently with that status; any other error or a `smtp.messageAcceptanceTimeoutMs` timeout refuses the envelope with a temporary `451`. Use `createAcceptedEnvelopeDispatchPlan()` inside the hook to snapshot the resolved plan for later replay-safe `dispatchAcceptedEnvelope()` calls. Both `AcceptEnvelopeRejectionError` and `IAcceptEnvelopeContext` are exported.
358
+ `hooks.onAcceptEnvelope` is the terminal durable owner for any envelope containing a resolved `store` or `deliver` action. It receives the exact raw RFC822 bytes (`rawMessage`): the message as the client sent it, with only the DATA dot-stuffing removed and the CRLF that ends the last line kept (RFC 5321 §4.1.1.4: the first CRLF of `<CRLF>.<CRLF>` belongs to the message), so the bytes can be relayed unchanged through `enqueueRawEmail()`, `sendRawOutboundEmail()` or an unsigned raw relay. The listener refuses a message that contains a bare CR or bare LF (RFC 5321 §2.3.8) with `550 5.6.0` after reading it to its end-of-data marker, without calling a hook, so every handed-off message uses canonical CRLF line endings. It also receives the complete per-recipient route resolution (`resolvedRecipientRoutes`), and the Rust security verdicts (`securityResults`). It must durably persist the message before resolving — the SMTP `250` is only sent after the hook resolves. Throwing an `AcceptEnvelopeRejectionError` with a `5xx` code rejects the envelope permanently with that status; any other error or a `smtp.messageAcceptanceTimeoutMs` timeout refuses the envelope with a temporary `451`. Use `createAcceptedEnvelopeDispatchPlan()` inside the hook to snapshot the resolved plan for later replay-safe `dispatchAcceptedEnvelope()` calls. Both `AcceptEnvelopeRejectionError` and `IAcceptEnvelopeContext` are exported.
359
359
 
360
360
  Use `smtp.securePort` when the implicit TLS listener uses a remapped backend port instead of public `465`; the configured secure port must also be present in `ports`, otherwise `start()` throws.
361
361
 
@@ -564,7 +564,7 @@ The `sendOutboundEmail` method:
564
564
 
565
565
  `sendRawOutboundEmail(host, port, rawMessage, envelope, { dkimDomain, dkimSelector })` provides the same transport behavior for exact bytes. It accepts only canonical CRLF-terminated message bytes and a canonical separate SMTP envelope, requires `dkimDomain` to equal the envelope sender domain, and loads the exact caller-supplied selector. It fails before SMTP if that selector/key cannot be loaded or Rust cannot sign the decoded bytes.
566
566
 
567
- Unsigned raw relay exists for one caller: a backup MX that holds mail received for a protected domain while its primary is down and later relays the received bytes to the primary unchanged. It cannot sign third parties' mail and must not re-sign it. It is off unless requested explicitly through the Rust bridge, `RustSecurityBridge.getInstance().sendRawEmail({ ...transport, envelopeFrom, envelopeTo, rawMessageBase64, signing: 'unsignedRelay' })`, and such a request must carry no `dkim` material; Rust refuses a request that mixes the two, names an unknown `signing` mode, or omits `dkim` without the opt-in. Authenticated submission, route actions, `sendRawOutboundEmail()`, the durable raw queue and configuration can never select it: those paths always send `signing: 'dkim'` with their resolved key. The relayed bytes go out byte for byte, so they keep the original sender's own `DKIM-Signature` and smartmta adds none; an empty `envelopeFrom` relays a bounce as `MAIL FROM:<>`, and an `outboundAuthenticationCaptureId` still captures the exact relayed bytes. Bytes received by the SMTP listener end with the CRLF of their last line, so a message whose sender used CRLF line endings only relays unchanged. The bytes must use canonical CRLF line endings and end with CRLF: Rust refuses a bare LF or CR, or a missing final CRLF, before SMTP with `errorType: 'raw_message'` and `retryable: false`, and a relay must treat that refusal as permanent because retrying the same stored bytes cannot succeed. `smartMtaCapabilities.explicitUnsignedRawRelay` advertises the option.
567
+ Unsigned raw relay exists for one caller: a backup MX that holds mail received for a protected domain while its primary is down and later relays the received bytes to the primary unchanged. It cannot sign third parties' mail and must not re-sign it. It is off unless requested explicitly through the Rust bridge, `RustSecurityBridge.getInstance().sendRawEmail({ ...transport, envelopeFrom, envelopeTo, rawMessageBase64, signing: 'unsignedRelay' })`, and such a request must carry no `dkim` material; Rust refuses a request that mixes the two, names an unknown `signing` mode, or omits `dkim` without the opt-in. Authenticated submission, route actions, `sendRawOutboundEmail()`, the durable raw queue and configuration can never select it: those paths always send `signing: 'dkim'` with their resolved key. The relayed bytes go out byte for byte, so they keep the original sender's own `DKIM-Signature` and smartmta adds none; an empty `envelopeFrom` relays a bounce as `MAIL FROM:<>`, and an `outboundAuthenticationCaptureId` still captures the exact relayed bytes. Bytes received by the SMTP listener use canonical CRLF line endings and end with the CRLF of their last line (the listener refuses a bare CR or LF with `550 5.6.0`), so received mail relays unchanged. The bytes must use canonical CRLF line endings and end with CRLF: Rust refuses a bare LF or CR, or a missing final CRLF, before SMTP with `errorType: 'raw_message'` and `retryable: false`, and a relay must treat that refusal as permanent because retrying the same stored bytes cannot succeed. `smartMtaCapabilities.explicitUnsignedRawRelay` advertises the option.
568
568
 
569
569
  When a connection proxy returns `sourceEvidence`, successful results and
570
570
  `SmtpDeliveryError` instances carry a non-enumerable
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmta',
6
- version: '13.0.1',
6
+ version: '13.1.0',
7
7
  description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
8
8
  }