-- NOTE: This example policy is not meant to be used as-is, and will require some editing. -- We strongly recommend reading the User Guide chapter on configuration before working with -- this example policy. See https://docs.kumomta.com/userguide/configuration -- This file must be written to /opt/kumomta/etc/policy/init.lua for use. -- This require statement is needed in any script passed to KumoMTA. -- Includes from this policy script will not need this declared again. local kumo = require 'kumo' local utils = require 'policy-extras.policy_utils' -- Load the policy helpers to simplify common configuration use cases local shaping = require 'policy-extras.shaping' local queue_module = require 'policy-extras.queue' local listener_domains = require 'policy-extras.listener_domains' local sources = require 'policy-extras.sources' local dkim_sign = require 'policy-extras.dkim_sign' local log_hooks = require 'policy-extras.log_hooks' -- START SETUP -- Configure the sending IP addresses that will be used by KumoMTA to -- connect to remote systems using the sources.lua policy helper. -- Note that defining sources and pools does nothing without some form of -- policy in effect to assign messages to the source pools you have defined. -- WARNING: THIS WILL NOT LOAD WITHOUT THE source.toml FILE IN PLACE -- SEE https://docs.kumomta.com/userguide/configuration/sendingips/ sources:setup { '/opt/kumomta/etc/policy/sources.toml' } -- Configure DKIM signing. In this case we use the dkim_sign.lua policy helper. -- WARNING: THIS WILL NOT LOAD WITHOUT the dkim_data.toml FILE IN PLACE -- See https://docs.kumomta.com/userguide/configuration/dkim/ local dkim_signer = dkim_sign:setup { '/opt/kumomta/etc/policy/dkim_data.toml' } -- Load Traffic Shaping Automation Helper local shaper = shaping:setup_with_automation { publish = { 'http://127.0.0.1:8008' }, subscribe = { 'http://127.0.0.1:8008' }, extra_files = { '/opt/kumomta/etc/policy/shaping.toml' }, } -- Send a JSON webhook to a local network host. -- See https://docs.kumomta.com/userguide/operation/webhooks/ log_hooks:new_json { name = 'webhook', url = 'http://10.0.0.1:4242/log', log_parameters = { headers = { 'Subject', 'X-Customer-ID' }, }, } -- Configure queue management settings. These are not throttles, but instead -- control how messages flow through the queues. -- WARNING: ENSURE THAT WEBHOOKS AND SHAPING ARE SETUP BEFORE THE QUEUE HELPER FOR PROPER OPERATION -- WARNING: THIS WILL NOT LOAD WITHOUT the queues.toml FILE IN PLACE -- See https://docs.kumomta.com/userguide/configuration/queuemanagement/ local queue_helper = queue_module:setup { '/opt/kumomta/etc/policy/queues.toml' } -- END SETUP --START EVENT HANDLERS -- Called On Startup, handles initial configuration kumo.on('init', function() -- Define the default "data" spool location; this is where -- message bodies will be stored. -- See https://docs.kumomta.com/userguide/configuration/spool/ kumo.define_spool { name = 'data', path = '/var/spool/kumomta/data', kind = 'RocksDB', } -- Define the default "meta" spool location; this is where -- message envelope and metadata will be stored. kumo.define_spool { name = 'meta', path = '/var/spool/kumomta/meta', kind = 'RocksDB', } -- Configure publishing of TSA logs to automation daemon shaper.setup_publish() -- Configure logging to local disk. Separating spool and logs to separate -- disks helps reduce IO load and can help performance. -- See https://docs.kumomta.com/userguide/configuration/logging/ kumo.configure_local_logs { log_dir = '/var/log/kumomta', max_segment_duration = '1 minute', -- headers = { 'Subject', 'X-Customer-ID' }, } -- Configure bounce classification. -- See https://docs.kumomta.com/userguide/configuration/bounce/ kumo.configure_bounce_classifier { files = { '/opt/kumomta/share/bounce_classifier/iana.toml', }, } -- Configure HTTP Listeners for injection and management APIs. -- See https://docs.kumomta.com/userguide/configuration/httplisteners/ kumo.start_http_listener { listen = '0.0.0.0:8000', -- allowed to access any http endpoint without additional auth trusted_hosts = { '127.0.0.1', '::1' }, } kumo.start_http_listener { use_tls = true, listen = '0.0.0.0:8001', -- allowed to access any http endpoint without additional auth trusted_hosts = { '127.0.0.1', '::1' }, } kumo.configure_log_hook { name = 'qq-webhook', headers = { 'Date', 'Message-Id', 'Subject' }, per_record = { OOB = { enable = false, }, Feedback = { enable = false, }, }, } -- Define an SMTP listener. Can be used multiple times with different -- parameters to define multiple listeners! -- See https://docs.kumomta.com/userguide/configuration/smtplisteners/ kumo.start_esmtp_listener { listen = '0.0.0.0:25', hostname = 'sys1.mydomain.com', tls_private_key = "/opt/kumomta/etc/tls/mydomain.com/key.pem", tls_certificate = "/opt/kumomta/etc/tls/mydomain.com/fullchain.crt", -- tls_certificate = '/opt/kumomta/cert.pem', -- tls_certificate = '/etc/pki/tls/certs', -- override the default set of relay hosts relay_hosts = { '127.0.0.1', 'x.x.x.x', 'x.x.x.y'}, } -- Use shared throttles rather than in-process throttles, do not enable -- without first installing and configuring redis. -- See https://docs.kumomta.com/reference/kumo/configure_redis_throttles/ -- kumo.configure_redis_throttles { node = 'redis://127.0.0.1/' } end) -- END OF THE INIT EVENT -- Configure listener domains for relay, oob bounces, and FBLs using the -- listener_domains.lua policy helper. -- WARNING: THIS WILL NOT LOAD WITHOUT THE listener_domains.toml FILE IN PLACE -- SEE https://docs.kumomta.com/userguide/configuration/smtplisteners/ kumo.on( 'get_listener_domain', listener_domains:setup { '/opt/kumomta/etc/policy/listener_domains.toml' } ) -- Configure traffic shaping using the shaping.lua policy helper. -- Commented out by default since we recommend using the Traffic Shaping Automation helper loaded below. -- WARNING: THIS WILL NOT LOAD WITHOUT AN ADDITIONAL SCRIPT IN PLACE -- SEE https://docs.kumomta.com/userguide/configuration/trafficshaping/ -- kumo.on('get_egress_path_config', shaping:setup { '/opt/kumomta/share/policy-extras/shaping.toml', '/opt/kumomta/etc/policy/shaping.toml', }) -- Call the Traffic Shaping Automation Helper to configure shaping rules. kumo.on('get_egress_path_config', shaper.get_egress_path_config) -- Processing of incoming messages via SMTP kumo.on('smtp_server_message_received', function(msg) -- Protect against SMTP Smuggling (https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/) local failed = msg:check_fix_conformance( -- check for and reject messages with these issues: 'NON_CANONICAL_LINE_ENDINGS', -- fix messages with these issues: '' ) if failed then kumo.reject(552, string.format('5.6.0 %s', failed)) end -- Call the queue helper to set up the queue for the message. queue_helper:apply(msg) -- SIGNING MUST COME LAST OR YOU COULD BREAK YOUR DKIM SIGNATURES dkim_signer(msg) end) -- Processing of incoming messages via HTTP kumo.on('http_message_generated', function(msg) -- Call the queue helper to set up the queue for the message. queue_helper:apply(msg) -- SIGNING MUST COME LAST OR YOU COULD BREAK YOUR DKIM SIGNATURES dkim_signer(msg) end) local delivery_endpoint = 'https://deliverywebhook.myinjector.com/api/event/kumomta'; kumo.on('should_enqueue_log_record', function(msg, hook_name) local log_record = msg:get_meta 'log_record' -- avoid an infinite loop caused by logging that we logged that we logged... -- Check the log record: if the record was destined for the webhook queue -- then it was a record of the webhook delivery attempt and we must not -- log its outcome via the webhook. if log_record.queue ~= 'qq-webhook' then -- was some other event that we want to log via the webhook msg:set_meta('queue', 'qq-webhook') return true end return false end) kumo.on('get_queue_config', function(domain, tenant, campaign, routing_domain) if domain == 'qq-webhook' then -- Use the `make.qq-webhook` event to handle delivery -- of webhook log records return kumo.make_queue_config { protocol = { custom_lua = { -- this will cause an event called `make.qq-webhook` to trigger. -- You can pick any name for this event, so long as it doesn't -- collide with a pre-defined event, and so long as you bind -- to it with a kumo.on call constructor = 'make.qq-webhook', }, }, -- Age out messages after being in the queue for max_age max_age = '20 minutes', -- If a message cannot be immediately delivered and encounters a transient failure, -- then a (jittered) delay of retry_interval seconds will be applied before trying again retry_interval = '5 minutes', -- Retry at most every max_retry_interval max_retry_interval = '10 minutes', } end return kumo.make_queue_config { -- Age out messages after being in the queue for max_age max_age = '20 minutes', -- If a message cannot be immediately delivered and encounters a transient failure, -- then a (jittered) delay of retry_interval will be applied before trying again retry_interval = '5 minutes', -- Retry at most every max_retry_interval max_retry_interval = '10 minutes', } end) -- This is a user-defined event that matches up to the custom_lua -- constructor used in `get_queue_config` below. -- It returns a lua connection object that can be used to "send" -- messages to their destination. kumo.on('make.qq-webhook', function(domain, tenant, campaign) local connection = {} local client = kumo.http.build_client {} function connection:send(message) local response = client :post(delivery_endpoint) :header('Content-Type', 'application/json') :body(message:get_data()) :send() local disposition = string.format( '%d %s: %s', response:status_code(), response:status_reason(), response:text() ) if response:status_is_success() then return disposition end -- Signal that the webhook request failed. -- In this case the 500 status prevents us from retrying -- the webhook call again, but you could be more sophisticated -- and analyze the disposition to determine if retrying it -- would be useful and generate a 400 status instead. -- In that case, the message we be retryed later, until -- it reached it expiration. kumo.reject(500, disposition) end return connection end) -- END configure QQ events -- END OF EVENT HANDLERS