Skip to content

Conversation

@AdityaAudi
Copy link
Contributor

Motivation

Email suppression lists are a critical compliance and deliverability requirement for production email platforms:

  1. Regulatory Compliance: CAN-SPAM, GDPR, CASL, and PECR mandate honoring unsubscribe requests
  2. Deliverability Protection: Prevents reputation damage from sending to bounced/complained addresses
  3. Operational Efficiency: Avoids wasted resources on undeliverable messages
  4. User Trust: Respects opt-out preferences

Currently, KumoMTA users must implement suppression logic externally. This PR adds native suppression list management.

What This PR Implements

HTTP REST API (8 Endpoints)

Endpoint Method Purpose
/api/admin/suppression/v1 POST Create/update entry
/api/admin/suppression/v1 GET List with filtering
/api/admin/suppression/v1 DELETE Remove entry
/api/admin/suppression/v1/{recipient} GET Get by recipient
/api/admin/suppression/v1/bulk POST Bulk create
/api/admin/suppression/v1/bulk/delete POST Bulk delete
/api/admin/suppression/v1/check POST Check suppression
/api/admin/suppression/v1/summary GET Statistics

Lua Policy API

  • kumo.api.admin.suppression.add{}
  • kumo.api.admin.suppression.remove{}
  • kumo.api.admin.suppression.check{}
  • kumo.api.admin.suppression.get()
  • kumo.api.admin.suppression.list{}
  • kumo.api.admin.suppression.summary()

Storage Backends

  • In-Memory (default): Fast, for development/testing
  • RocksDB: Persistent, recommended for production

Configuration

kumo.on('init', function()
  kumo.configure_suppression_store {
    kind = 'rocks_db',
    path = '/var/spool/kumomta/suppression',
    flush = true,
    compression = 'lz4',
  }

What This PR Does NOT Include

Important: This PR provides the infrastructure for suppression list management.
It does NOT automatically suppress bounces, complaints, or unsubscribes.
Users must implement their own policy logic using the provided Lua API.

This design is intentional because different users have different suppression requirements (e.g., suppress on all 5xx vs specific codes, immediate vs after N failures).

Example Policy Implementations

Users can implement automatic suppression by adding policy code to their configuration:

Example 1: Auto-suppress on hard bounce

 kumo.on('smtp_client_rewrite_delivery_status', function(response, msg)
   if response:is_code(5) then
     kumo.api.admin.suppression.add {
       recipient = msg:recipient().email,
       type = "transactional",
       source = "bounce",
     }
   end
   return response

Example 2: Check suppression before sending

local result = kumo.api.admin.suppression.check {
   recipients = { "[email protected]" },
   type = "non_transactional"
 }
 if result.results["[email protected]"] then
   -- Recipient is suppressed
 end

Testing

  • 13 unit tests - all passing
  • RocksDB persistence verified
  • Manual E2E testing completed

Performance

Operation Complexity
check O(1)
add O(1)
remove O(1)

@AdityaAudi AdityaAudi force-pushed the feature/suppression-list-api branch from ebab7c5 to 7d80152 Compare December 20, 2025 23:51
@AdityaAudi AdityaAudi force-pushed the feature/suppression-list-api branch from 83ef20b to 4f7cba0 Compare December 21, 2025 00:23
@AdityaAudi AdityaAudi marked this pull request as ready for review December 21, 2025 05:37
@wez
Copy link
Collaborator

wez commented Dec 21, 2025

Thanks for this! It's not really the right shape for kumomta though. Here's a quick collection of thoughts to add some colour/rationale. Please forgive me if they are a bit terse; it's early morning for me and I just want to get these out of mind ahead of the Christmas season so that I can relax a bit more!

  • Both the in-memory and rocksdb storage implementations are only suitable for single-node kumomta deployments. For a clustered/multi-node deployment there would need to be some kind of synchronization/replication or use of an external data storage solution (eg: a sql database of some kind, or a lookup via some external web service).
  • Most of the customer configurations we've reviewed that include suppression list management have requirements for a combination of domain and/or domain pattern matching functionality that wouldn't fit in the storage solution included here, so a more sophisticated lookup function would be required to efficiently implement matching in that situation
  • The kumod service itself probably should not be the one to implement list management. The management/storage of the list should probably be a separate service and kumod should be consuming that. With that in mind, this PR is sort of the opposite of the functionality that I think most kumomta users would need in kumod itself. We've been discussing some things around this sort of management topic for our roadmap, but don't have concrete details to share publicly at this time.

I'd like to suggest and request that you hold off doing further work on this PR for the moment, and that we can chat via eg: Discord to discuss your requirements and the direction that we're thinking of going on this, so that our mutual efforts are better aligned!

@AdityaAudi
Copy link
Contributor Author

AdityaAudi commented Dec 21, 2025

@wez Thank you for the detailed feedback!
I appreciate you taking the time to explain the architectural direction and rationale, it's very helpful.

For context: There's no specific requirement from our end for this feature. I was being proactive in contributing functionality that I noticed exists in other MTA infrastructure software and thought could be valuable for KumoMTA users. I should have engaged with the team earlier to understand the roadmap before diving into implementation!

Your points are well-taken:

  • Single-node constraint - The RocksDB/in-memory storage doesn't address clustered deployments where suppression state needs to be synchronized across nodes.
  • Pattern matching - The exact-match implementation wouldn't handle the domain/wildcard patterns (*@domain.com, subdomain patterns) that production suppression lists typically require.
  • Architecture - I now understand the concern about embedding list management in kumod itself. Having kumod consume an external suppression service (rather than being the service) makes much more sense for:
    • Separation of concerns
    • Natural clustering (shared external backend)
    • Flexibility in storage and matching implementations
    • Integration with existing customer infrastructure

I'll hold off on further work on this PR as requested. Happy to close this if that's preferred, or keep it as a reference point for discussion.

I'd love to join the Discord conversation to better understand the direction you're thinking and see how I can contribute in a way that aligns with the roadmap.

Thanks again for the thoughtful response, and happy holidays! 🎄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants