NEWS¶
exploder branch¶
(merged ~2026/08)
Reimplement Exploder functionality in StorageWriterFilter. Backwards-compatible with existing configs but we expect to deprecate Exploder in the future.
StorageWriterFilter now directly orchestrates fan-out of rcpts from a downstream smtp tx to multiple/per-rcpt upstream/output tx and then fans-in the upstream responses.
Exploder adds a lot of overhead (almost 2x rest request time, db writes, etc) due to adding a second trip through most of the stack whereas most transactions only have 1 recipient.
This creates some additional complexity in that exploder gives you a single point to reject the message whereas now there must be some synchronization across upstream tx Transaction Groups. We think this is a good tradeoff vs the large performance benefit.
Migration Guide¶
Look at the diffs to router.yaml 1b7256b9 for an example. In
the sample configs, smtp submission/ingress sender/tags inject into a
_exploder chain. These ends with an exploder filter instance.
remove exploder filter from the end of the _exploder chain.
Merge the exploder instance’s
output_chaininto the _exploder chain including the finalrest_outputmsa: true->sf_mode: upstream_unavailabilitymsa: false->sf_mode: mixed_data_responseadd
mail_okfilter before firstrouter
dns_policy branch¶
(merged 2026/3/17)
Add DnsResolutionFilterOutput dns_resolution
drive-by improvements:
set smtp server ident
smtp client logging
signals_rcpt branch¶
(merged 2026/3/6)
Add per_rcpt policy_action mode to allow writing rules to reject individual recipients. Signals&Policies
Matcher updates:
add num_rcpts matcher
add per_rcpt support to address_list matcher
add invalid_mail_from/rcpt_to matchers
bugfixes/improvements¶
receivers handle heartbeats, validate content-type
aiosmtpd passes null-reverse-path to handler as <> but the koukan stack expects empty string
domain_from_address() catch exceptions parsing the address
recipient router support no policy = route everything
This is a better way to write the catchall since address_list doesn’t
match invalid address.
before:
- filter: router
policy:
name: address_list
# no domains/dest -> reject all
after:
- filter: router
# no policy: match everything
# no dest: reject
RestEndpoint fail the tx if we got that far without endpoint/base_url. This can happen with a bad routing config. Previously this would result in an uncaught exception.
Signals branch¶
(merged 2026/2/26)
This introduces machinery to take exceptional actions on messages matching specified criteria. Signals&Policies
This includes the following new filters:
Incompatible changes¶
relay_auth filter replaced with simple matcher¶
before:
endpoint:
- name: submission
chain:
- filter: relay_auth
after:
endpoint:
- name: submission
chain:
- filter: policy_action
match:
not:
matcher: smtp_auth
tag: smtp_auth
action: REJECT
code: 550
message: '5.7.1 not authorized'