-
DcRouter v20.1.0
StableRelease / build-and-release (push) Successful in 10m24sreleased this
2026-09-15 22:32:10 +00:00 | 93 commits to main since this release2026-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,coreMailGatewayPrepareOutboundHandoffandcoreMailGatewayGetOutboundStatus, with dcrouter callingcoreMailGatewayResolveRecipients,coreMailGatewayPrepareInboundHandoff,coreMailGatewayCompleteInboundHandoffandcoreMailGatewayCompleteOutboundHandoffback 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,syncCoreMailGatewayPeeranddeleteCoreMailGatewayPeer. Peers are validated by the sharednormalizeCoreMailGatewayPeerDesiredState, so only argon2id-v1 verifier material with exactly one current credential is accepted, and onecoreMailServiceIdcan never be claimed by a second owner.transferOriginis 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
enqueueRawEmailkeyed on the CoreMail submission id. Delivery status is served from a durable snapshot merged with live queue state, is monotonic pertransportMessageId, and answers a terminal status for an unknown id so a workload learns instead of polling forever. - Add the
coreMailinbound 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, treatsunhandledas 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
listPageon 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/interfacesto 30.14.0 and@design.estate/dees-catalogto 9.8.0.
- Serve the dcrouter half of the CoreMail contract on the ops TypedSocket surface:
Fixes
- stop losing accepted inbound mail silently (inbound-durability)
- Stop generating an accepting inbound route for a
typedEndpointaddress whose identity has outbound SMTP disabled. Such an address can never receive: registration goes throughauthenticateMailCredential, which requiressmtp.enabled, and Cloudly only provisions the credential when outbound is enabled — while the accepting route was generated onidentity.enabledalone. 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 defaultstorepolicy it is filed into the domain catch-all and retained for 30 days rather than lost, and a domain configured withdefaultAction: 'reject'refuses at RCPT so the sender is told.smtpForwardis 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
CachedEmaildefault 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-terminalrather than reusingemail-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".
- Stop generating an accepting inbound route for a
Downloads
- add the CoreMail mail gateway (coremail-gateway)