• DcRouter v20.1.0
    Release / build-and-release (push) Successful in 10m24s
    Stable

    jkunz released this 2026-09-15 22:32:10 +00:00 | 93 commits to main since this release

    2026-09-15 - 20.1.0

    Features

    • add the CoreMail mail gateway (coremail-gateway)
      • Serve the dcrouter half of the CoreMail contract on the ops TypedSocket surface: coreMailGatewayAuthenticate, coreMailGatewayPrepareOutboundHandoff and coreMailGatewayGetOutboundStatus, with dcrouter calling coreMailGatewayResolveRecipients, coreMailGatewayPrepareInboundHandoff, coreMailGatewayCompleteInboundHandoff and coreMailGatewayCompleteOutboundHandoff back on the connected peer. CoreMail authenticates per connection with an argon2id credential; the session lives on the connection, never in a TypedSocket tag, and every other gateway method is refused without it.
      • Provision gateway peers through the owner-scoped gateway-client requests listCoreMailGatewayPeers, syncCoreMailGatewayPeer and deleteCoreMailGatewayPeer. Peers are validated by the shared normalizeCoreMailGatewayPeerDesiredState, so only argon2id-v1 verifier material with exactly one current credential is accepted, and one coreMailServiceId can never be claimed by a second owner. transferOrigin is producer-owned control-plane data and is never inferred from a socket, Host header or peer input.
      • Move outbound mail as exact bytes: CoreMail's MIME is fetched once through its one-time GET grant, verified against the grant's SHA-256 and length, checked for canonical CRLF, acknowledged to CoreMail exactly once, and enqueued through smartmta's enqueueRawEmail keyed on the CoreMail submission id. Delivery status is served from a durable snapshot merged with live queue state, is monotonic per transportMessageId, and answers a terminal status for an unknown id so a workload learns instead of polling forever.
      • Add the coreMail inbound target type: an address bound to a CoreMail service generates an accepting process route without requiring outbound SMTP, is consulted at RCPT time against its own provisioned service, honours CoreMail's reject and defer codes, treats unhandled as fall-through to dcrouter's existing decision, and hands the accepted message to CoreMail from the spool before the typed-endpoint attempt.
      • Implement deletion-stable listPage on the SmartMTA blob storage manager over SmartBucket keyset pagination, and admit the /email/queue.idempotency/ namespace the durable raw outbound queue writes, so exact-bytes outbound delivery and bounded queue recovery both work.
      • Upgrade @serve.zone/interfaces to 30.14.0 and @design.estate/dees-catalog to 9.8.0.

    Fixes

    • stop losing accepted inbound mail silently (inbound-durability)
      • Stop generating an accepting inbound route for a typedEndpoint address whose identity has outbound SMTP disabled. Such an address can never receive: registration goes through authenticateMailCredential, which requires smtp.enabled, and Cloudly only provisions the credential when outbound is enabled — while the accepting route was generated on identity.enabled alone. The result was a 250 to the sending MTA followed by deferral until the message was destroyed. Mail for these addresses now follows the domain's inbound policy instead: under the default store policy it is filed into the domain catch-all and retained for 30 days rather than lost, and a domain configured with defaultAction: 'reject' refuses at RCPT so the sender is told. smtpForward is unaffected, since the hub opens its own connection at delivery time and needs nothing registered up front.
      • Apply that same predicate when selecting the recipient identity for typed-endpoint delivery, so acceptance and delivery cannot disagree. A registered peer tag is never removed or revalidated when an identity changes, so an app that registered while outbound was enabled kept a live tag afterwards and carried on receiving until its socket next dropped — at which point re-registration failed and mail was silently lost again. The two decisions now share one definition of a deliverable identity.
      • Raise the delivery budget for accepted inbound mail from the CachedEmail default of 3 attempts to 12. At five minutes apart, three attempts is a ten-minute tolerance — shorter than an ordinary rolling deploy of a receiving app, and exceeding it destroys the message with no bounce, because the 250 was already sent. The budget is only ever raised, so an explicitly lowered value on a specific email is still honoured.
      • Report a terminal loss as email-spool-failed-terminal rather than reusing email-spool-deferred, and log it at error level stating that no bounce will follow. Both outcomes previously carried the same reason, so an operator watching the queue could not distinguish "will retry" from "destroyed".
    Downloads