PowerMTA

PowerMTA Lua Scripting: 5 Practical Examples for Routing & Retries

Get hands-on PowerMTA Lua scripting examples that show how to route by domain, override retry logic based on SMTP codes, and prioritise high-value queues. Includes testing tips and a managed-SMTP comparison.

By Shane0 views
PowerMTA Lua Scripting: 5 Practical Examples for Routing & Retries

If you run PowerMTA at any real volume, the stock configuration only gets you so far. To control which IP pool carries which recipient domain, how many times a deferred message retries, and which queue gets priority, you need the Lua scripting layer. This article collects practical powermta lua scripting examples you can copy into your own environment, covering routing, retry logic, and queue policies.

By the end, you will be able to write Lua hooks that select a VirtualMTA based on recipient domain, override PowerMTA's default retry behaviour when specific SMTP error codes appear, and raise queue priority for high-value domains. You will also see how to test and debug Lua scripts before rolling them into production.

How Lua Hooks Work in PowerMTA

PowerMTA embeds a Lua interpreter and lets you register a Lua file in the main configuration with a directive such as lua-file /etc/pmta/scripts/routing.lua. Once registered, PowerMTA calls functions inside that file at fixed points in the delivery lifecycle. The most common points are when a connection is opened, when a message is accepted, when individual recipients are processed, and after a delivery attempt completes.

# /etc/pmta/pmta.conf
lua-file /etc/pmta/scripts/routing.lua

The table below shows the typical hook points and what you can influence at each stage.

Hook pointTypical function nameWhat you can change
Connectionpmta.on_connect()VirtualMTA, source IP, rate limits
Messagepmta.on_message()Queue priority, message metadata
Recipientpmta.on_recipient()Per-recipient VMTA, retry flags
Deliverypmta.on_delivery()Retry, bounce, log decisions

The examples below use the common pmta.get_recipient() and pmta.get_message() accessors, plus pmta.log() for debugging. Always consult the Lua API reference for your exact PowerMTA version before relying on a property name.

Example 1: Route by Recipient Domain or IP Pool

A common requirement is to send Gmail-bound mail from one set of IPs and Outlook-bound mail from another. This keeps each pool's reputation isolated and lets you warm up IPs independently. The following pmta.on_recipient() hook inspects the recipient domain and sets the VirtualMTA on the connection accordingly. The VirtualMTA names must match those defined in your pmta.conf.

function pmta.on_recipient()
    local rcpt = pmta.get_recipient()
    local conn = pmta.get_connection()
    local domain = rcpt.domain:lower()

    if domain == "gmail.com" then
        conn:set("vmta", "gmail-pool")
    elseif domain == "outlook.com" or domain == "hotmail.com" then
        conn:set("vmta", "outlook-pool")
    else
        conn:set("vmta", "default-pool")
    end
end

After reloading PowerMTA, send a test message to a Gmail address and confirm in the delivery logs that the gmail-pool VirtualMTA was used. If the domain does not match, the default pool applies. You can also add a pmta.log("routing to gmail-pool for " .. domain) line to trace the decision in real time.

Example 2: Custom Retry Logic Based on SMTP Error Codes

PowerMTA already retries deferred messages, but its default policy may not match your needs. For example, you may want to stop retrying after a 550 permanent failure, or back off more aggressively after a 421 service-not-available deferral. The following hook runs after each delivery attempt and inspects the SMTP status code.

function pmta.on_delivery()
    local rcpt = pmta.get_recipient()
    local status = rcpt:get("delivery_status")
    local code = tonumber(rcpt:get("smtp_code") or "0")

    if status == "failed" and code == 550 then
        rcpt:set("retry", "false")
        rcpt:set("bounce", "true")
    elseif status == "deferred" and code == 421 then
        rcpt:set("retry", "true")
        rcpt:set("retry_delay", "1800")
    end
end

A 550 code usually means the mailbox does not exist, so retrying wastes resources and harms your sending reputation. A 421 code often signals a temporary problem at the receiving server; forcing a 30-minute delay instead of PowerMTA's default can reduce the chance of repeated deferrals. Always test with a controlled domain before applying this globally, because some providers use 421 for rate limiting and a shorter delay may be more appropriate.

Example 3: Dynamic Queue Priority for High-Value Domains

If you send large batches, PowerMTA's queue can become a bottleneck. You can use Lua to ensure that mail to your most engaged or highest-revenue domains leaves before bulk traffic. The pmta.on_message() hook below assigns a higher queue priority for specific domains.

function pmta.on_message()
    local msg = pmta.get_message()
    local rcpt = pmta.get_recipient()
    local domain = rcpt.domain:lower()

    if domain == "gmail.com" or domain == "yahoo.com" then
        msg:set("queue_priority", "high")
    else
        msg:set("queue_priority", "normal")
    end
end

The queue_priority value maps to the queue-priority settings in your pmta.conf. A high priority queue is processed before the normal queue. Be careful not to mark too many messages as high priority, otherwise the distinction becomes meaningless.

Testing and Debugging Lua Scripts in PowerMTA

Before deploying a Lua change, validate the configuration syntax and check the logs. Use pmta --test-config to catch syntax errors, and then reload PowerMTA with pmta reload. If your script has a runtime error, PowerMTA logs it and continues with the default behaviour, so you may need to raise the log level.

pmta --test-config
pmta reload
tail -f /var/log/pmta/log

Follow this sequence every time you edit a Lua file:

  1. Write the Lua file and place it in a known path such as /etc/pmta/scripts/routing.lua.
  2. Add the lua-file directive to pmta.conf.
  3. Run pmta --test-config to catch syntax errors.
  4. Reload and send a test message to a controlled domain.
  5. Watch the log for Lua error entries or unexpected behaviour.

If you manage several PowerMTA servers, repeating these Lua changes by hand across each host is tedious. PMTAcore is a Windows desktop application that automates PowerMTA installation over SSH and centralises configuration tasks such as DNS record generation and IP rotation. The Lua scripts in this article still apply once PowerMTA is installed; PMTAcore can help you manage the infrastructure around them. See the PowerMTA Management page for details.

When you combine custom retry logic with multi-server campaigns, PMTAcore's Campaign Manager can enforce per-server rate limits while your Lua hooks decide retry behaviour. For bulk senders who also need a self-hosted marketing platform with open and click tracking, Choco Mailer is a separate paid addon.

Monitoring your sending IPs for blacklist entries is essential when you change retry or routing policies, because a sudden spike in retries can hurt reputation. PMTAcore's IP Blacklist Checker streams DNSBL results for single IPs or whole server groups.

Managed SMTP vs PowerMTA Lua: A Quick Comparison

If you are evaluating this level of control against a managed SMTP API, the common questions still apply. Is Mailgun better than SendGrid? In independent 2026 tests, Mailgun tends to offer more flexible SMTP and better log visibility, while SendGrid wins on SDK breadth and marketing features. What is better than SendGrid? For developers who need per-recipient routing and retry overrides, PowerMTA with Lua scripting provides control no cloud API exposes. Which transactional email service is the best? There is no single best; it depends on volume, deliverability requirements, and whether you want to manage infrastructure. PowerMTA is a self-hosted MTA, so you own the IPs and the queue, which is exactly where Lua scripting matters.

Start with the free trial. For full licence options, see the pricing page.

#powermta lua scripting examples#powermta lua tutorial#powermta lua routing example#powermta queue management lua#powermta retry logic#lua hooks#email deliverability