init.lua Policy
KumoMTA policy is written in Lua. The init.lua file defines listeners, spool, logging, routing, delivery paths, and event hooks.
PING8 uses KumoMTA as the delivery engine. KumoMTA accepts injected messages, queues them, delivers them to recipient providers, and sends lifecycle events back to PING8.
Official reference: Configuring KumoMTA (opens in a new tab).
Where it lives
Install policy on the KumoMTA host:
/opt/kumomta/etc/policy/init.luaKeep secrets out of this file. Read deployment-specific values from the environment file.
1. Load KumoMTA
local kumo = require 'kumo'The kumo module exposes KumoMTA policy functions such as HTTP listeners, spool definition, queues, egress sources, and log hooks.
2. Read required environment values
local PING8_WEBHOOK_URL =
os.getenv('PING8_WEBHOOK_URL') or 'https://app.yourdomain.com/api/kumo/events'
local PING8_WEBHOOK_TOKEN =
assert(os.getenv('PING8_WEBHOOK_TOKEN'), 'PING8_WEBHOOK_TOKEN must be set')
local PROXY_HOST = os.getenv('KUMOPROXY_HOST') or 'proxy.yourdomain.com'
local PROXY_PORT = tonumber(os.getenv('KUMOPROXY_PORT') or '5000')
local PROXY_PUBLIC_IP = os.getenv('KUMOPROXY_PUBLIC_IP') or '203.0.113.20'
local PROXY_HELO = os.getenv('KUMOPROXY_HELO') or 'proxy.yourdomain.com'Fail fast when required secrets are missing. A server that starts without webhook authentication will be harder to troubleshoot later.
3. Start a localhost HTTP listener
kumo.on('init', function()
kumo.start_http_listener {
listen = '127.0.0.1:8000',
trusted_hosts = { '127.0.0.1/32' },
}
end)This listener accepts injection and metrics requests only through local Nginx. The official trusted_hosts setting controls which hosts can access the HTTP service: trusted_hosts (opens in a new tab).
Do not bind this listener publicly unless you have a deliberate security design around KumoMTA's HTTP service.
4. Configure logs and spool
kumo.configure_local_logs {
log_dir = '/var/log/kumomta',
max_segment_duration = '5min',
}
kumo.define_spool { name = 'data', path = '/var/spool/kumomta/data' }
kumo.define_spool { name = 'meta', path = '/var/spool/kumomta/meta' }Spool is operational state. Size disks for queue spikes, keep the filesystem healthy, and alert before disk pressure becomes a delivery outage.
5. Define KumoProxy egress
kumo.make_egress_source {
name = 'proxy-default',
socks5_proxy_server = string.format('%s:%d', PROXY_HOST, PROXY_PORT),
socks5_proxy_source_address = PROXY_PUBLIC_IP,
ehlo_domain = PROXY_HELO,
}
kumo.make_egress_pool {
name = 'shared-default',
entries = { { name = 'proxy-default' } },
}Use KumoProxy when the visible outbound SMTP source should be the proxy host. Recipient providers evaluate the proxy egress IP, PTR/rDNS, HELO, SPF, DKIM, DMARC alignment, and traffic behavior.
Do not use source_address to represent the proxy's public IP. source_address binds on the local KumoMTA host. With SOCKS5 egress, use socks5_proxy_source_address so the proxy uses the correct outbound source address. Official references: egress sources (opens in a new tab) and SOCKS5 source address (opens in a new tab).
6. Route queues to the pool
kumo.on('get_queue_config', function(domain, tenant, campaign, routing_domain)
if domain == 'ping8.log-hook' then
return kumo.make_queue_config {
protocol = {
custom_lua = { constructor = 'ping8_webhook_sender' },
},
}
end
return kumo.make_queue_config {
egress_pool = 'shared-default',
retry_interval = '5min',
max_retry_interval = '24h',
}
end)This keeps normal recipient delivery on the outbound pool and routes log-hook events to a custom webhook sender.
Tune retry policy deliberately. Very aggressive retry can worsen provider throttling. Very slow retry can delay recovery after a temporary provider issue.
7. Define the egress path
kumo.on('get_egress_path_config', function(domain, source_name, site_name)
return kumo.make_egress_path {
enable_tls = 'OpportunisticInsecure',
smtp_port = 25,
ip_lookup_strategy = 'Ipv4Only',
}
end)Most recipient MX delivery uses TCP port 25. Opportunistic TLS is common for SMTP delivery, but production policy may need stricter settings for specific routes.
Traffic shaping should normally be layered here later with KumoMTA's shaping helper or Traffic Shaping Automation. See Traffic Shaping and Warmup.
8. Configure delivery event log hooks
kumo.configure_log_hook {
name = 'ping8',
headers = {
'Subject',
'X-PING8-Campaign-Id',
'X-PING8-Task-Id',
'X-PING8-Recipient-Id',
'X-PING8-Api-Log-Id',
'X-PING8-Message-Id',
'X-PING8-Sending-Profile-Id',
},
}The log hook creates event messages that can be queued and delivered to an external system. Official reference: configure_log_hook (opens in a new tab).
Keep correlation headers aligned with the headers your PING8 deployment emits. If correlation headers are missing, PING8 may receive delivery events but fail to attach them to the correct campaign, API send, or recipient.
9. Enqueue log records to the webhook queue
kumo.on('should_enqueue_log_record', function(msg)
msg:set_meta('queue', 'ping8.log-hook')
return true
end)This sends every matching log record to the custom webhook queue declared in get_queue_config.
10. POST lifecycle events back to PING8
kumo.on('ping8_webhook_sender', function(domain, tenant, campaign)
local sender = {}
function sender:send(message)
local body = message:get_data()
local response = kumo.http.build_client({})
:post(PING8_WEBHOOK_URL)
:header('Content-Type', 'application/json')
:header('X-PING8-Kumo-Token', PING8_WEBHOOK_TOKEN)
:body(body)
:send()
if response:status_is_success() then
return string.format('%d ok', response:status_code())
end
kumo.reject(500, string.format(
'ping8 webhook returned %d: %s',
response:status_code(),
response:text() or '(empty body)'
))
end
return sender
end)Webhook failure should be visible and retryable. Do not silently drop lifecycle events. PING8 needs those events for delivered, deferred, bounced, expired, complained, and failed telemetry.
DKIM signing
KumoMTA can sign messages with DKIM in policy. The exact selector and key path depend on how your domains are managed.
Use KumoMTA's DKIM guide as the implementation reference: Configuring DKIM Signing (opens in a new tab).
PING8 domain verification should agree with:
- DKIM selector.
- Public DKIM TXT record.
- Private key location and permissions.
- Sender domain alignment.
What to avoid
- Do not put real tokens in
init.lua. - Do not expose KumoMTA's HTTP listener directly to the internet.
- Do not use one shared egress pool for unrelated brands if reputation isolation matters.
- Do not skip webhook correlation headers.
- Do not increase volume before provider responses and webhook events are visible in PING8.