@push.rocks/smartmta 13.0.0 → 13.0.1

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026-10-15 - 13.0.1
4
+
5
+ ### Fixes
6
+
7
+ - Inbound SMTP DATA keeps the CRLF that ends the message's last line (RFC 5321 §4.1.1.4: the first CRLF of `<CRLF>.<CRLF>` belongs to the message); since 5220ee0 the Rust DATA reader removed it as part of the terminator. The `rawMessage` handed to `onAcceptEnvelope`/`onMessageData` gains those two bytes and is now the exact received message (dot-stuffing removed), so received mail can be passed unchanged to `sendRawEmail({ signing: 'unsignedRelay' })`, `sendRawOutboundEmail()` and `enqueueRawEmail()`, which refused it before for a missing final CRLF. DKIM, SPF and DMARC verdicts, parsed `Email` fields and the SIZE limit are unchanged. Consumers that appended a CRLF themselves must stop (CoreMail).
8
+ - A message larger than `maxMessageSize` is now read to its end-of-data marker and then refused with 552, and the session continues. Before, the 552 was sent as soon as the limit was crossed and the rest of the message was read as SMTP commands, so lines in an oversized body (`RSET`, `MAIL FROM`, `RCPT TO`, `DATA` and a further message) were executed.
9
+ - Commands pipelined behind the end-of-data marker in the same segment (RFC 2920), such as `.<CRLF>QUIT<CRLF>` or the next transaction's `MAIL FROM`/`RCPT TO`/`DATA`, are answered in order. Before, the DATA reader discarded every byte after the marker in the chunk it had read, and the client waited for replies that never came.
10
+
3
11
  ## 2026-10-15 - 13.0.0
4
12
 
5
13
  ### Breaking Changes
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "f8dab1778aa8426956cec532226ea2f30c2d5a87e4b469b6c7d4c1b2ab6f6fdd",
3
+ "binarySha256": "26d34a7674c6b5b739c8ad6764c4e7d5282534e0b577459f1eb7f2d6b73e5543",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartmta",
6
- "projectVersion": "13.0.0",
7
- "gitCommit": "703440756f7b7b48e0ae105d94fa393c91318e72",
6
+ "projectVersion": "13.0.1",
7
+ "gitCommit": "e7bc2248fd9d2e90a3299bd05bfdabefa193f008",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-15T10:07:12.782Z",
9
+ "builtAt": "2026-10-15T11:23:05.139Z",
10
10
  "tsrustVersion": "4.6.0",
11
11
  "binary": "mailer-bin",
12
12
  "target": "linux_amd64_musl"
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "format": "tsrust.build-provenance.v2",
3
- "binarySha256": "1cc0210225bf2b5d9d501e7a126a178cec8617fe0f7c23acd54d0dc96f8adee0",
3
+ "binarySha256": "0c3a0c4ad7a774b1797521e287aad257f2bf10a65bd8d2b7cdc35917a8692113",
4
4
  "buildInfo": {
5
5
  "projectName": "@push.rocks/smartmta",
6
- "projectVersion": "13.0.0",
7
- "gitCommit": "703440756f7b7b48e0ae105d94fa393c91318e72",
6
+ "projectVersion": "13.0.1",
7
+ "gitCommit": "e7bc2248fd9d2e90a3299bd05bfdabefa193f008",
8
8
  "gitDirty": false,
9
- "builtAt": "2026-10-15T10:09:24.772Z",
9
+ "builtAt": "2026-10-15T11:25:18.459Z",
10
10
  "tsrustVersion": "4.6.0",
11
11
  "binary": "mailer-bin",
12
12
  "target": "linux_arm64_musl"
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartmta',
6
- version: '13.0.0',
6
+ version: '13.0.1',
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.0",
3
+ "version": "13.0.1",
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",
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
273
+ maxMessageSize: 25 * 1024 * 1024, // 25 MB; a larger message is read to its end and refused with 552
274
274
  maxClients: 500,
275
275
  });
276
276
 
@@ -353,9 +353,9 @@ Without this, an authenticated submission addressed to a domain this server host
353
353
 
354
354
  `hooks.onRcptTo`, `hooks.onMessageData`, and `hooks.onAcceptEnvelope` are awaited asynchronously. If a hook throws or times out, SmartMTA returns a temporary SMTP failure. The hook context includes `abortSignal`; long-running consumers should observe it and stop work promptly. A custom `onRcptTo` hook may accept local recipients using application-specific lookup, but external relay is still blocked unless a matched route explicitly sets `allowRelay: true`.
355
355
 
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`. 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.
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 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. 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. 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 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.
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.0',
6
+ version: '13.0.1',
7
7
  description: 'A high-performance, enterprise-grade Mail Transfer Agent (MTA) built from scratch in TypeScript with Rust acceleration.'
8
8
  }