diff --git a/README.md b/README.md index ab3ba7b7..cb3553df 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # kumomta +## Concepts + +### Queuing + +Messages are assigned to a queue upon reception. The system can support +arbitrarily named queues but the convention is to construct the queue name from +some metadata associated with the message: + +* `tenant` - some kind of sender or customer identifier or identity derived + either from the message itself or authentication of the injection session. +* `campaign` - a sender-provided label that can be used to logically group a set + of related messages, perhaps generated from the same campaign. +* destination domain - the site where the email will be routed + +These three pieces of information are combined to produce the name of the queue +in the form `campaign:tenant@domain`. + ## Debugging/Tracing This will launch the server using the policy defined in [simple_policy.lua](simple_policy.lua): diff --git a/crates/kumod/src/smtp_server.rs b/crates/kumod/src/smtp_server.rs index e8127f2b..0e138c35 100644 --- a/crates/kumod/src/smtp_server.rs +++ b/crates/kumod/src/smtp_server.rs @@ -415,12 +415,19 @@ impl SmtpServer { ids.push(message.id().to_string()); - let queue_name = match message.get_meta("queue")? { - serde_json::Value::String(name) => name.to_string(), - serde_json::Value::Null => message.recipient()?.domain().to_string(), - value => anyhow::bail!( - "expected 'queue' metadata to be a string value, got {value:?}" - ), + let queue_name = match message.get_meta_string("queue")? { + Some(name) => name.to_string(), + None => { + let campaign = message.get_meta_string("campaign")?; + let tenant = message.get_meta_string("tenant")?; + let domain = message.recipient()?.domain().to_string(); + match (campaign, tenant) { + (Some(c), Some(t)) => format!("{c}:{t}@{domain}"), + (Some(c), None) => format!("{c}:@{domain}"), + (None, Some(t)) => format!("{t}@{domain}"), + (None, None) => domain, + } + } }; if queue_name != "null" { diff --git a/crates/message/src/message.rs b/crates/message/src/message.rs index f95ab29b..4bfaf7b8 100644 --- a/crates/message/src/message.rs +++ b/crates/message/src/message.rs @@ -227,6 +227,20 @@ impl Message { } } + /// Retrieve `key` as a String. + pub fn get_meta_string( + &self, + key: S, + ) -> anyhow::Result> { + match self.get_meta(key) { + Ok(serde_json::Value::String(value)) => Ok(Some(value.to_string())), + Ok(serde_json::Value::Null) => Ok(None), + hmm => { + anyhow::bail!("expected '{key}' to be a string value, got {hmm:?}"); + } + } + } + pub fn get_meta( &self, key: S,