Provider & MTA setup

KumoMTA SMTP test: listeners, STARTTLS, and AUTH PLAIN

Test KumoMTA SMTP submission on port 587 or 25. Configure start_esmtp_listener and smtp_server_auth_plain, verify relay with a live transcript.

11 min read Credentials redacted No signup

Connection settings

Host
your-kumo-hostname.example.com
Port
587
Security
STARTTLS

KumoMTA is a high-performance, Lua-configured MTA for large-scale sending. There is no fixed SMTP hostname: you define your own ESMTP listeners with kumo.start_esmtp_listener, and the operator owns every layer: listener, TLS certificate, auth hook, relay policy. A protocol-level test matters because logs only tell you what happened after the fact, while a live transcript shows what the server advertises and accepts before anything reaches the queue.

Recommended settings

New to protocol testing? What an SMTP test is explains the stages you will see in the transcript.

Choosing listener ports: 25 vs 587

Run two listeners with distinct roles. Port 25 is for relay from trusted infrastructure: application servers, other MTAs, internal networks. Port 587 is for authenticated submission: users, outbound gateways, anything outside your trusted CIDRs.

-- Trusted relay: no AUTH, IP-based permission only
kumo.start_esmtp_listener {
  listen = '0.0.0.0:25',
  hostname = 'mail.example.com',
  relay_hosts = { '10.0.0.0/8', '192.168.1.0/24' },
}

-- Authenticated submission: AUTH required, STARTTLS enforced
kumo.start_esmtp_listener {
  listen = '0.0.0.0:587',
  hostname = 'mail.example.com',
  tls_certificate = '/etc/kumomta/certs/mail.example.com.pem',
  tls_private_key = '/etc/kumomta/certs/mail.example.com.key',
}

The separation pays off twice: you can enforce TLS before AUTH on 587 without breaking legacy internal senders on 25, and a failed test immediately tells you which permission model is in play (IP allowlist on 25, credentials plus relay hook on 587). Never expose 587 to the internet with an empty or permissive smtp_server_auth_plain; the listener will accept mail from anyone.

In the 587 block, hostname appears in the EHLO greeting and should match your DNS and certificate. A common mistake is binding to 127.0.0.1 on a box that also runs your application, then wondering why external tests get connection refused; bind 0.0.0.0 (or a specific public interface) and control access with firewall rules and relay policy.

Implementing AUTH PLAIN

KumoMTA authenticates clients through the smtp_server_auth_plain event, a Lua function that receives the username and password from the AUTH PLAIN exchange and returns true or false:

kumo.on('smtp_server_auth_plain', function(authz, authc, password)
  -- authz is usually empty; authc is the username
  if authc == 'injector@example.com' and password == 'correct-secret' then
    return true
  end
  return false
end)

For production, validate against a secrets store (Vault, environment variables, or a password file) rather than hardcoding credentials. KumoMTA's documentation covers Vault-backed authentication patterns.

Two key points:

Verifying what your listener advertises

Run SMTP Tester in handshake-only mode (no message) and read the EHLO response in the transcript; a correctly configured submission listener advertises:

250-mail.example.com Hello [203.0.113.7]
250-STARTTLS
250-AUTH PLAIN
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250 HELP

Check three things:

  1. STARTTLS is present on port 587. If missing, your tls_certificate/tls_private_key paths are wrong or unreadable by the kumomta process.
  2. AUTH PLAIN appears only after STARTTLS completes; on the pre-TLS EHLO you should see STARTTLS but no AUTH.
  3. SIZE matches your intended max message size; if missing or too small, larger mail bounces with a 552.

If you see AUTH LOGIN advertised, a proxy in front of KumoMTA is answering EHLO; credentials will still fail because the handler only implements PLAIN.

Reading the transcript stage by stage

The transcript is a chronological record of the client and server dialogue. Each stage isolates one layer of your configuration, so a failure at a specific line points at a specific knob in init.lua:

Stage Lines to look for What it proves
Connect 220 mail.example.com ... Listener reachable, hostname matches config
EHLO 250- capability list Pre-TLS: STARTTLS present, no AUTH
STARTTLS 220 Ready to start TLS + handshake Certificate loads, TLS negotiates
EHLO (after TLS) 250-AUTH PLAIN Auth hook registered
AUTH PLAIN 334, then 235 2.7.0 Accepted smtp_server_auth_plain returned true
MAIL FROM 250 2.1.0 OK Envelope sender accepted
RCPT TO 250 2.1.5 OK or 550/554 Relay permission granted or denied
DATA 354, then 250 ... queued as ... Message entered the spool

Two habits are worth building. Read the last server line before any error; that code is the authoritative answer. And compare the two EHLO responses side by side: most misconfigurations show up as a missing capability in one of those blocks rather than as an explicit error elsewhere.

Testing with SMTP Tester

  1. Set host to your KumoMTA server's public hostname or IP.
  2. Port 587, security STARTTLS.
  3. Auth method: PLAIN (Auto may also work if the server advertises only PLAIN).
  4. Enter the credentials your smtp_server_auth_plain handler expects.
  5. From address: whatever your relay policy allows.
  6. Click Run. The transcript shows EHLO → STARTTLS → AUTH PLAIN → 235.

A 235 confirms authentication; a 250 with a queue ID means the message entered the spool.

Handshake-only testing against production

Full tests inject a real message into the queue, rarely what you want on a production MTA during a change or incident. Handshake-only mode stops after AUTH (or after EHLO on unauthenticated listeners): it proves TLS, capability advertising, and credentials without queuing anything. Use it after upgrades or init.lua changes, certificate rotations, and firewall or load-balancer changes that may break the client-visible path while the daemon is healthy. Run it from an external network so you test the same path your applications take.

Testing relay permissions: external vs localhost

Relay policy is where most KumoMTA surprises live: the same test can succeed from your jump host and fail from a laptop on a home connection. Test both paths:

Test path Expected result on port 25 Expected result on port 587
From a relay_hosts CIDR 250, no AUTH exchange 250 after AUTH
From an external network 550 relay denied (or 554) on RCPT TO 250 after AUTH, if the relay hook grants authenticated sessions
AUTH with bad credentials n/a 535 authentication failed
AUTH on plaintext session n/a 530 Must issue a STARTTLS command first

Many deployments accept unauthenticated injection from trusted servers on port 25 via relay_hosts: connections from listed CIDRs relay without AUTH. Test from an allowed network with security set to "none" (or STARTTLS if certs are configured) and empty credentials; the transcript should show a clean 250 on RCPT TO with no AUTH exchange. If external authenticated users get relay denied despite a 235, your relay hook is not checking the authenticated identity and must allow sessions that completed AUTH:

-- In a relay_message_generated or similar hook
if msg:get_meta('authc') then
  -- allow
end

Internal-but-not-external relay after auth is the signature of a missing authc check.

Using the transcript to confirm DKIM and header behavior

Once a message is accepted, KumoMTA signs it according to the domain, selector, and key configuration in your policy script, with the signing identity usually taken from the envelope sender. The transcript cannot show the signature (it is added after injection), but it confirms the two things that determine whether signing and attribution work:

A practical verification loop: send one test message to a Gmail or Microsoft 365 inbox you control, then check the delivered message for the DKIM d= domain and the Received header; if either is wrong, fix the envelope sender or the listener hostname and re-run. The Gmail SMTP test and Outlook 365 SMTP test guides cover the receiving side.

Comparing with swaks and telnet

If you already use swaks or telnet, SMTP Tester covers the same ground without remembering flags or typing base64 by hand:

Task swaks / telnet SMTP Tester
Check advertised AUTH swaks --server host:587, read EHLO Handshake-only run; capabilities in transcript
AUTH PLAIN test swaks --auth PLAIN --auth-user u --auth-password p Select PLAIN, enter credentials
STARTTLS check openssl s_client -starttls smtp -connect host:587 TLS panel: protocol, cipher, cert subject and expiry

telnet handles quick loopback checks (telnet localhost 25, type EHLO) but cannot do STARTTLS, and typing AUTH PLAIN base64 into a terminal leaves the credential in your shell history. Hosted providers like Amazon SES remove most of this surface; on a self-hosted MTA the protocol is exposed to you directly.

Common operator errors

535 "Authentication failed"

Credentials do not match what your Lua handler expects. Check case sensitivity, trailing whitespace, and which listener you reached (multiple listeners can have their own auth logic). If your client negotiated AUTH LOGIN against a proxy in front of KumoMTA, force PLAIN.

550 "Relaying denied" / 554 after successful auth

After authentication, the server still checks relay policy. A relay error despite 235 AUTH OK means the configuration is not granting relay rights to authenticated sessions; add the authc check shown above. A 554 on RCPT TO with an authenticated session usually means the same.

Connection refused / connection timeout

Verify the listener is bound to an externally reachable address (not 127.0.0.1), that firewall rules allow 587 and 25 inbound, and that KumoMTA is running. A refused connection with the daemon up usually means the port is not in the listen set; ss -tlnp | grep -E ':(25|587)' settles it.

TLS handshake failures

AUTH not advertised at all

If neither EHLO response contains an AUTH line, the smtp_server_auth_plain hook is not registered for the listener you reached. Confirm the kumo.on('smtp_server_auth_plain', ...) call executes in your policy script, that you are testing the right port, and that the script loaded without errors; a Lua exception during load can leave hooks unregistered while the daemon appears healthy.

421 and transient failures

A 421 Service not available on connect or EHLO usually means the listener is throttling, out of resources, or shutting down. Check the daemon log and connection limits before assuming a config bug; a single handshake-only run keeps the probe footprint minimal.

Security notes

Related guides

Fixing SMTP authentication error 535 covers the 535 error above in more depth, and the PowerMTA SMTP test guide covers the other common self-hosted MTA. For port selection see SMTP port 587 vs 465, and for alternative tools see SMTP Tester vs smtper.net.

Frequently asked questions

Why does the transcript show no AUTH in the EHLO response before STARTTLS?

KumoMTA hides AUTH capabilities on unencrypted sessions to avoid leaking that authentication exists and to force clients onto TLS. Complete STARTTLS first, then issue EHLO again; the second response advertises AUTH PLAIN.

How do I test relay permissions without sending real mail?

Use handshake-only mode. It walks through connection, EHLO, STARTTLS, and AUTH but stops before MAIL FROM and RCPT TO, so nothing is queued. For relay policy you need the RCPT stage, so run one full test to a mailbox you control and read the exact response line.

What does a 250 with a queue ID actually confirm?

That KumoMTA accepted the message into its spool: listener, TLS, auth, and relay policy all passed. Delivery is a separate matter; check the queue and logs.

Can a test damage my production queue?

Handshake-only mode queues nothing. A full test queues exactly one message to the recipient you specify, which is negligible for a production MTA, but point it at a mailbox you control. During an incident, avoid repeated full tests: the extra queue entries make triage harder.

My applications connect from inside the datacenter. Why test from outside?

External tests exercise the full client-visible path: firewall, any load balancer or proxy, and the listener binding. A config that works over loopback can still be unreachable from the internet, and the failure mode tells you which layer diverges.

How often should I run a test against production KumoMTA?

Run handshake-only after every config change, certificate renewal, upgrade, or network change, and a full end-to-end test to a controlled mailbox on a regular schedule (weekly is a reasonable baseline for production).

Try it on your own server

Run these settings against your SMTP server and watch the live, credential-redacted protocol transcript.

Open SMTP Tester