Policy to rewrite the domain sender address

Hello.

We are currently testing KumoMTA and encountering difficulties with a specific point.

Our main domain is being impacted by automated application emails, so we have created a subdomain for them. However, due to the difficulty of changing the source address directly within the applications, we are trying to implement a sender rewrite rule in the MTA. The goal is to have messages originating from addresses such as app1@domain, app2@domain, etc., rewritten as app1@sub.domain, app2@sub.domain, and so on.

I am specifically trying to understand if smtp_client_mail_from is the right hook for this, or if I should be using pre_helper_modify_message to rewrite both the Envelope and the From: header to ensure DMARC alignment.

My current configuration:

rewrite.lua

local rewrite = {}

local sender_map = {
["app1@domain"] = "app1@sub.domain",
}

function rewrite.apply(sender)
  local lower = string.lower(sender)
  if sender_map[lower] then
    kumo.log_info(string.format("[REWRITE] %s -> %s", sender, sender_map[lower]))
    return sender_map[lower]
  end
  return sender
end
return rewrite

init.lua

kumo.on('smtp_client_mail_from', function(msg, mail_from)
  local original_sender = mail_from:get_address()
  local rewritten = rewrite.apply(original_sender)
  if rewritten ~= original_sender then
    mail_from:set_address(rewritten)
    kumo.log_info(string.format('[EGRESS-REWRITE] %s -> %s id=%s', original_sender, rewritten, msg:get_id()))
  end
end)

However, the rewrite is not happening. I suspect I might be mishandling the mail_from object or using the wrong event for this purpose.

**Version:** kumod 2026.04.09-ea3b2a9b

Any help would be greatly appreciated. Thanks in advance!

smtp_client_mail_from ?

I think only have smtp_server_mail_from in kumomta . smtp_server_mail_from - KumoMTA Docs

Maybe you can use this code.

  local rewrite = require 'rewrite'

  local function rewrite_sender(msg)
    local sender = msg:sender()
    local original = sender.email
    if not original then
      return
    end

    local rewritten = rewrite.apply(original)
    if rewritten == original then
      return
    end

    msg:set_sender(rewritten)

    local mime = msg:parse_mime()
    local from = mime.headers:from()

    if from then
      for _, mailbox in ipairs(from) do
        if mailbox.address and mailbox.address.local_part and mailbox.address.domain then
          local current = string.lower(mailbox.address.local_part .. '@' .. mailbox.address.domain)
          if current == string.lower(original) then
            local local_part, domain = rewritten:match('^(.+)@([^@]+)$')
            mailbox.address.local_part = local_part
            mailbox.address.domain = domain
          end
        end
      end

      mime.headers:set_from(from)
      msg:set_data(tostring(mime))
    end

    kumo.log_info(string.format('[REWRITE] %s -> %s id=%s', original, rewritten, msg:id()))
  end

  kumo.on('smtp_server_data', function(msg, conn_meta)
    rewrite_sender(msg)
  end)

  -- Also use this for HTTP injection if applicable:
  kumo.on('http_message_generated', function(msg, auth_info)
    rewrite_sender(msg)
  end)

Key point: run this before DKIM signing. If you rewrite From: after signing, you will break the signature. For DMARC, rewriting only the envelope sender may be enough only when SPF alignment is acceptable; if you want the visible/domain identity to move to sub.domain, rewrite both envelope sender and From: then DKIM-sign with the subdomain.

I am guessing you used an AI to help you with this because smtp_client_mail_from does not exist.

smtp_server_mail_fromdoes and might be what you want to use.

Neither does pre_helper_modify_message

If you want to rewrite the envelope, you may want to rebuild the message and craft the sender().email var.

Hi, everyone,

Thank you sou much for the feedback and clarifications, Jack and Tom!

Tom, you were spot on - the ghost functions were indeed the result of some bad AI-generated scaffolding during my initial research. As my background is primarily in Postfix (or Sendmail, if you remember that :wink: ), I’m still wrapping my head around KumoMTA’s architecture. Those functions seemed logical to me at the time.

Following your advice, using Jack’s code and reading through the Kumo docs again, I adapted the configuration to use the smtp_server_message_received event and ensured that the address rewriting happens at the very beginning of the block, before any DKIM signing takes place. As you said, this keeps the signatures valid and ensures DMARC alignment.

Here’s a snippet of how the init.lua looks now with the recommended changes:

kumo.on('smtp_server_message_received', function(msg)

  msg:check_fix_conformance('NON_CANONICAL_LINE_ENDINGS', '')

  local subject = msg:get_first_named_header_value("Subject") or ""
  msg:set_meta("subject", subject)

  local sender = msg:sender()
  local original = sender.email

  if original then
    local rewritten = rewrite.apply(original)

    if rewritten ~= original then
      msg:set_meta("original_sender", original)
      
      msg:set_sender(rewritten)

      local mime = msg:parse_mime()
      local from = mime.headers:from()

      if from then
        for _, mailbox in ipairs(from) do
          if mailbox.address and mailbox.address.local_part and mailbox.address.domain then
            local current = string.lower(mailbox.address.local_part .. '@' .. mailbox.address.domain)
            if current == string.lower(original) then
              local local_part, domain = rewritten:match('^(.+)@([^@]+)$')
              mailbox.address.local_part = local_part
              mailbox.address.domain = domain
            end
          end
        end
        mime.headers:set_from(from)
        msg:set_data(tostring(mime))
      end

      kumo.log_info(string.format('[REWRITE] %s -> %s id=%s', original, rewritten, msg:id()))
    end
  end

  local current_sender = msg:sender().email or ""

  if current_sender:match('[%@%.]domain%.com%.br$') then
    local ok, err = pcall(function()
      dkim_signer(msg)
    end)

    if not ok then
      kumo.log_error(string.format("[DKIM] signing failed: %s", tostring(err)))
    end
  end

end)

The regex [%@%.]dominio%.com%.br$ at the end ensures that both the main domain and any rewritten subdomains (we will have at least one, but probably two or three more) match the DKIM conditional check dynamically.

Testing has been successful so far, and the outbound emails are now reaching Gmail/Outlook with perfect alignment. Thanks again for steering me in the right direction!

Best regards,