Testing and Verifying a Mail Server

September 6, 2026

system-administration email tls

After building a mail server across multiple posts — Postfix, Dovecot, DKIM/SPF/DMARC, Rspamd, ClamAV, PostfixAdmin, and Roundcube — the components need end-to-end verification. This post covers testing every layer of the stack from TLS connectivity through to webmail access and email authentication.

Seeding Test Data

Before testing, create a test domain and mailbox through PostfixAdmin or the CLI:

php /var/www/mailadmin.example.com/htdocs/scripts/postfixadmin-cli.php domain add example.com
php /var/www/mailadmin.example.com/htdocs/scripts/postfixadmin-cli.php mailbox add mailtest@example.com \
  --password "test-password" --password2 "test-password" --name "Mail Test" --active 1

Verify the mailbox exists in the database:

mysql mailserver -e "SELECT username, domain, maildir FROM mailbox WHERE username = 'mailtest@example.com';"

Testing SMTP Connectivity

Port 25 — Incoming Mail

telnet localhost 25

You should see the Postfix banner:

220 mail.example.com ESMTP

Type EHLO test and verify TLS and the milter are advertised:

250-STARTTLS

Type QUIT to disconnect.

Port 587 — Submission (Authenticated)

Test TLS on the submission port:

openssl s_client -connect localhost:587 -starttls smtp

After the TLS handshake, verify the certificate subject matches mail.example.com and the connection shows 250-AUTH PLAIN LOGIN.

Port 465 — SMTPS (Implicit TLS)

openssl s_client -connect mail.example.com:465

The TLS handshake should complete immediately (no STARTTLS needed). Verify the Postfix greeting appears after the encrypted connection is established.

Testing IMAP Connectivity

Port 993 — IMAPS

openssl s_client -connect localhost:993

After the TLS handshake, you should see the Dovecot banner:

* OK [CAPABILITY IMAP4rev1 ...] Dovecot ready.

Test Authentication

doveadm auth test mailtest@example.com test-password

This tests the full chain — Dovecot queries MySQL, verifies the password hash, and returns success or failure. A successful result looks like:

passdb: mailtest@example.com auth succeeded
extra fields:
  user=mailtest@example.com

Test IMAP Login

openssl s_client -connect localhost:993 -quiet

After connecting, type:

a LOGIN mailtest@example.com test-password
a LIST "" "*"
a LOGOUT

This authenticates, lists all mailbox folders, and disconnects. A new mailbox should show at least INBOX.

Testing Mail Delivery

Local Delivery via LMTP

Send a test message through Postfix and verify Dovecot delivers it:

echo "Test message body" | mail -s "Delivery test" mailtest@example.com

Check the mail log for delivery confirmation:

tail -20 /var/log/mail.log

Look for status=sent with delivered to maildir. Verify the message arrived on disk:

ls /var/vmail/example.com/mailtest/Maildir/new/

A file should appear in the new/ directory.

Verify Sieve Filtering

Send a message that triggers the spam Sieve rule. If Rspamd is running, the easiest test is to send the GTUBE test string:

echo "XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X" | \
  mail -s "Sieve test" mailtest@example.com

If the message was scored as spam and the Sieve script filed it:

ls /var/vmail/example.com/mailtest/Maildir/.Junk/new/

The GTUBE message should appear in the Junk folder.

Testing TLS Certificates

Verify Certificate for All Hostnames

Each hostname used in MX records, IMAP, and SMTP should be valid in the certificate's Subject Alternative Names:

openssl s_client -connect mail.example.com:993 -servername mail.example.com 2>/dev/null | \
  openssl x509 -noout -text | grep -A1 "Subject Alternative Name"

Verify all expected hostnames appear: mail.example.com, imap.example.com, smtp.example.com.

Check Certificate Expiry

openssl s_client -connect mail.example.com:993 -servername mail.example.com 2>/dev/null | \
  openssl x509 -noout -dates

Verify notAfter is more than 30 days away. If using Let's Encrypt, the certificate renews automatically every 60-90 days.

Testing Email Authentication

Verify DKIM Signing

Send a message to an external address and inspect the headers. Or test locally:

opendkim-testkey -d example.com -s mail -vvv

This queries DNS for the DKIM public key and validates it matches the private key. A successful result shows key OK.

Verify SPF Record

dig +short TXT example.com | grep spf

The result should show your SPF record, e.g.:

"v=spf1 mx a:mail.example.com -all"

Verify DMARC Record

dig +short TXT _dmarc.example.com

Expected result:

"v=DMARC1; p=reject; rua=mailto:postmaster@example.com"

External Verification Services

Send a test message to check-auth@verifier.port25.com. The reply includes a report showing whether DKIM, SPF, and DMARC passed. Alternatively, send to a Gmail address and inspect the headers — Gmail shows authentication results in the Authentication-Results header.

Testing Spam Filtering

Verify Rspamd Is Processing Mail

curl -s http://127.0.0.1:11334/stat | python3 -m json.tool

This returns statistics including scanned (total messages processed), learned (Bayes training count), and actions (reject, greylist, add header).

Test GTUBE Detection

rspamc < /usr/share/rspamd/gtube.eml

The output should show a high spam score and GTUBE in the symbols list.

Verify Greylisting

Send a message from an unknown sender. Check the log for a greylisting entry:

grep "greylist" /var/log/mail.log | tail -5

The first attempt should be temporarily rejected. A second attempt after 5 minutes should succeed.

Testing Antivirus

Verify ClamAV Is Running

clamdscan /etc/hostname

Should return OK.

Test EICAR Detection via Rspamd

Rspamd's ClamAV integration only scans MIME attachments — not inline message body text (scan_text_mime = false by default). To trigger detection, send the EICAR string as a file attachment:

# Create the EICAR test file (exact 68-byte string)
python3 -c "print('X5O!P%@AP[4\\\\PZX54(P^)7CC)7}\$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!\$H+H*', end='')" > /tmp/eicar.txt

# Send as an attachment
echo "Virus attachment test" | mail -s "EICAR test" -a /tmp/eicar.txt mailtest@example.com

# Clean up
rm /tmp/eicar.txt

Check the Rspamd log for rejection:

grep "CLAM_VIRUS" /var/log/rspamd/rspamd.log | tail -5

The message should be rejected with the CLAM_VIRUS symbol. If you put the EICAR string in the message body instead of an attachment, ClamAV won't scan it — this is expected behavior, not a misconfiguration.

Testing Web Interfaces

Roundcube

Navigate to https://mail.example.com/ and verify:

  1. The login page loads
  2. Login succeeds with mailtest@example.com credentials
  3. The inbox shows test messages sent earlier
  4. Composing and sending a test email works
  5. Settings > Filters shows the ManageSieve interface
curl -sk https://mail.example.com/ | grep -o 'rcmloginuser\|Roundcube Webmail'

This should return a match confirming the login page is served.

PostfixAdmin

Navigate to https://mailadmin.example.com/ and verify:

  1. The login page loads
  2. Superadmin login works
  3. The domain list shows example.com
  4. The mailbox list shows mailtest@example.com

Rspamd Web Interface

Access the Rspamd dashboard (via SSH tunnel or reverse proxy):

curl -s http://127.0.0.1:11334/stat

The web interface at port 11334 provides real-time graphs of spam vs. ham ratios, top symbols, and throughput.

Service Health Checks

Verify all services are running and listening:

# Postfix
ss -lnt sport = :25
ss -lnt sport = :587

# Dovecot
ss -lnt sport = :993
ss -lnt sport = :4190

# Rspamd
ss -lnt sport = :11332
ss -lnt sport = :11334

# ClamAV
ls -la /var/run/clamav/clamd.sock

# MySQL
ss -lnt sport = :3306

# Redis
ss -lnt sport = :6379

# Apache
ss -lnt sport = :80
ss -lnt sport = :443

All should return results showing the respective services are bound.

Check Service Logs for Errors

tail -20 /var/log/mail.log
tail -20 /var/log/clamav/clamd.log
tail -20 /var/log/clamav/freshclam.log
tail -20 /var/log/roundcube/errors.log

No errors should appear during normal operation. Common post-setup issues include file permission problems, socket path mismatches, and database connection failures.

DNS Verification

Verify all DNS records resolve correctly from an external perspective:

# MX record
dig +short MX example.com

# A record for mail hostname
dig +short A mail.example.com

# PTR (reverse DNS) — must match the MX hostname
dig +short -x $(dig +short A mail.example.com)

# SPF
dig +short TXT example.com | grep spf

# DKIM
dig +short TXT mail._domainkey.example.com

# DMARC
dig +short TXT _dmarc.example.com

The PTR record is critical — many mail servers reject messages from IPs without a matching reverse DNS entry. Contact your hosting provider if the PTR record doesn't match your mail hostname.

End-to-End Test

The final verification is a round-trip test: send mail to an external provider and receive a reply.

  1. Log in to Roundcube at https://mail.example.com/
  2. Send a test message to a Gmail, Outlook, or other external address
  3. Check the received message's headers for:
    • Authentication-Results showing dkim=pass, spf=pass, dmarc=pass
    • No spam folder delivery
  4. Reply from the external address
  5. Verify the reply arrives in Roundcube's inbox
  6. Verify the reply is not in the Junk folder

If authentication passes and messages flow both directions, the mail server is fully operational.

Summary

A complete mail server verification covers:

  • SMTP connectivity on ports 25, 587, and 465 with correct TLS
  • IMAP access on port 993 with working authentication
  • LMTP delivery from Postfix to Dovecot with Sieve filtering
  • DKIM signing verified via DNS key lookup
  • SPF and DMARC records correctly published
  • Rspamd processing all incoming mail with spam scoring
  • ClamAV scanning attachments with virus rejection
  • Roundcube serving webmail with ManageSieve integration
  • PostfixAdmin managing domains and mailboxes
  • DNS records (MX, A, PTR, SPF, DKIM, DMARC) all resolving correctly
  • End-to-end message flow to and from external providers

The mail server is now complete. For an overview of the entire build process, see Building a Complete Mail Server from Scratch.


Other Recent Posts