Route to "Smarthost"

Tests are ongoing

After reviewing the documentation (Custom Destination Routing - KumoMTA Docs), I couldn’t find a straightforward way to achieve the following:
Is there a gateway function that bypasses the MX records for a domain and routes the email directly to a specified gateway host?

Would the following approach be correct to achieve this behavior?

kumo.on('smtp_server_message_received', function(msg, conn_meta)
  msg:set_meta('queue', 'smarthost')
end)

And in /opt/kumomta/etc/queues.toml:

[queue.'smarthost']
routing_domain = '[my.server.ip]'

Or should I can only directly use msg:set_meta('queue', '[my.server.ip]') in the script?

Forgive me if I’m missing something, but is there a way (or is it planned) to handle this at the Tenant level within queues.toml to keep everything in the configuration and avoid excessive Lua scripting or embedding variables in the code?

Sorry for the comparison, but something similar to the gateway feature in Momentum (gateway), which works at the binding, binding_group, domain, and global scope.

Ooh a Momentum user, you guys are more rare. :wink:

There are a few different ways to achieve that sort of routing. One is to directly set the queue name as you mentioned above. That will cause all smart hosted mail to be scheduled to the same scheduled queue, which can make it more difficult to reason about its contents.

You can also do msg:set_meta('routing_domain', '[my.server.ip]') which will set the routing domain to an IP literal. That will cause the message to be queued to a queue named something like gmail.com![my.server.ip] for a message that would otherwise go direct to gmail.com. This allows you to see and manage the constituent queues before they feed into your smart host ready queue.

Another option is more complex, which is to override the queue configuration with a custom mx list. This one is closer to how momentum’s gateway option worked back in the day, but the interface to it is more powerful and a bit more complex: protocol - KumoMTA Docs

We don’t currently have a simple way to express this sort of function in our queues helper, but what I would suggest is to use a simple map in lua (which you could easily spin out to a separate json file if that is preferable) and do something like:

local smart_hosts = {['gmail.com'] = '[my.server.ip]'}
msg:set_meta('routing_domain', smart_hosts[msg:sender().domain])

actually, it looks like we do have an option for routing_domain in the queues helper!

so I think you can do:

[tenant.'mytenant']
routing_domain = '[my.server.ip]'

I’m not super clear on your criteria for this, so I’m not sure if any of these suggestions match up to what you want

Thank you Wez! I’ll run some tests and evaluate what you suggested to see if I can easily achieve what I want.

PS: I quickly tried to set:

[tenant.'Tenant1']
routing_domain = '[my.smarthost.ip]'

But:

Oct 18 16:56:03 kumod[197506]: /opt/kumomta/share/policy-extras/queue.lua:193 /opt/kumomta/share/policy-extras/queue.lua:215: /opt/kumomta/share/policy-extras/queue.lua:193 TenantConfig: unknown field 'routing_domain'
Oct 18 16:56:03 kumod[197506]: stack traceback:
Oct 18 16:56:03 kumod[197506]:         [C]: in function 'error'
Oct 18 16:56:03 kumod[197506]:         /opt/kumomta/share/policy-extras/typing.lua:78: in method 'raise'
Oct 18 16:56:03 kumod[197506]:         /opt/kumomta/share/policy-extras/typing.lua:396: in metamethod 'newindex'
Oct 18 16:56:03 kumod[197506]:         /opt/kumomta/share/policy-extras/queue.lua:193: in upvalue 'parse_config'
Oct 18 16:56:03 kumod[197506]:         /opt/kumomta/share/policy-extras/queue.lua:215: in function </opt/kumomta/share/policy-extras/queue.lua:212>```

hmm, looks like it needs to be in a tenant/domain or tenant/campaign/domain block. I’m not sure why it is restricted that way, but I also don’t remember writing that logic in the first place, so I’ll need to revisit the history to figure that out :slightly_smiling_face:

:slightly_smiling_face: oky
In the meantime, I will continue with what you suggested and wish everyone a good weekend.

I’m not super clear on your criteria for this, so I’m not sure if any of these suggestions match up to what you want

I’ll try to explain it briefly without giving too much unnecessary context.

Basically, all email that arrive on **some ** Listener ( tipically public ) must necessarily pass through our system (smarthost) before being sent externally. This system performs operations on the database and much more.

So, emails that enter through the public listeners need to pass through our smarthost, then return to the MTA via a private listener, and finally be sent out using the public IP pools that we choose.

With Momentum, I can easily accomplish this process with two config files ( that defines Listeners and Bindings ) and a small “static” Lua script, and I could probably achieve the same result if I could do something like:

[tenant.'Tenant1']
routing_domain = '[my.smarthost.ip]'

PS: Of course, I’m not expecting KumoMTA to behave exactly how I want, but I’m providing additional context because, not being familiar with KumoMTA, there may be solutions I’m not seeing.

…but I will continue my tests with your suggestions :slightly_smiling_face:

If you can share a sanitized version of your Momentum Lua script, that would be helpful.

for now, you can do some variant of this:

local smart_host_by_tenant = {['Tenant1'] = '[my.server.ip]'}
msg:set_meta('routing_domain', smart_host_by_tenant[msg:get_meta('tenant')])

after you’ve assigned the tenant; eg: using the queue helper apply function

A very simple pseudocode:

ESMTP_Listener {
  Listen "publicip:25" {
    Peer "0.0.0.0/0" {
        context = [
           customer = "ABC"
        ]
    }
  }
}

Binding_Group "relay_smarthost0" {
  Binding "172.0.0.1" {
    Gateway = "10.110.32.51"
  }
}

Binding_Group "relay_smarthost1" {
  Binding "172.0.0.2" {
    Gateway = "10.110.32.52"
  }
}

smarthost = ec_rand 2;
vctx_customer_id = vctx_conn_get "customer";
if ( vctx_customer_id ) {
  vctx_mess_set "bindingGroup" "relay_smarthost${smarthost}";
}

I don’t know if this will be enough.

This way, I add a new listener to the configuration file, and everything works without touching any additional code or maps.

But I don’t want to bother you further; it’s only fair that I spend some time figuring out the correct path with Kumo myself now :grinning_face_with_smiling_eyes:

Wish everyone a good weekend

Awesome, thanks.

So, we use message metadata directly to replace the use of context variables.
for instance, instead of using context = [customer = "ABC"] and vctx_customer_id = vctx_conn_get "customer";
we would use msg:set_meta('customer','ABC') and stored_customer_id = msg:get_meta('customer')

Bindings and binding groups are roughly equivalent to egress sources and egress pools, except that you can only assign to a pool.
If you are using the helpers, you could define:

  source_address = "10.110.32.51"

[source."ip-2"]
source_address = "10.110.32.52"

[pool."pool-1"]
[pool."pool-1"."ip-1"]

[pool."pool-1"]
[pool."pool-1"."ip-2"]```

^^ This allows you to assign a message to pool-1 or pool-2 in order to use the corresponding IPs

Now in the get_queue_config function, you can do something like this:

  myval = random(2)
  local params = {
    egress_pool = "pool-" .. myval,
  }
  return kumo.make_queue_config(params)
end)```

The above is not tested, but should work as is.

Of course we could probably convert it all for you for a small PS fee. This would not be our first Momentum conversion :wink:

Thank you, Tom, for the proposal and for the suggestions above (including Wez’s). Please note that I installed KumoMTA only 6 days ago as a ‘playground,’ and I am still in a very early phase of testing/scouting/evaluation, so I am not ready to make any decisions of any kind. I will definitely take this factor into consideration.