diff --git a/CHANGELOG.md b/CHANGELOG.md index 2155872d..79887e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ If you are upgrading from v0.16.x, replace the binary (or run `docker pull`). If - OAuth: Rework access tokens to an `AES-256-GCM-SIV` AEAD format that carries the account name for proxy routing and revokes tokens on credential change. ## Fixed +- MTA: Sub-addressing with external directories returns `550 Mailbox not found`. ## [0.16.7] - 2026-05-28 diff --git a/crates/common/src/network/mta.rs b/crates/common/src/network/mta.rs index 681ec0aa..413337eb 100644 --- a/crates/common/src/network/mta.rs +++ b/crates/common/src/network/mta.rs @@ -108,19 +108,28 @@ impl Server { .get_directory_for_cached_domain(&domain) .filter(|directory| directory.can_lookup_recipients()); if let Some(directory) = directory { - let address = if local_part.as_ref() == local_part_orig { - Cow::Borrowed(rcpt) - } else { + let is_subaddressed = local_part.as_ref() != local_part_orig; + let address = if is_subaddressed { Cow::Owned(format!("{local_part}@{domain_part}")) + } else { + Cow::Borrowed(rcpt) }; match directory.recipient(address.as_ref()).await? { Recipient::Account(account) => { Box::pin(self.synchronize_account(account)).await?; - return Ok(RcptResolution::Accept); + return Ok(if is_subaddressed { + RcptResolution::Rewrite(address.into_owned()) + } else { + RcptResolution::Accept + }); } Recipient::Group(group) => { Box::pin(self.synchronize_group(group)).await?; - return Ok(RcptResolution::Accept); + return Ok(if is_subaddressed { + RcptResolution::Rewrite(address.into_owned()) + } else { + RcptResolution::Accept + }); } Recipient::Invalid => {} }