smtpbench --help

Features

SMTPBench takes key=value arguments, with no dashes and no flags. Four are required; everything else tunes scale, realism, transport, or output. Every option it accepts is listed here, with defaults as the tool actually behaves.

Accurate as of SMTPBench 1.2.1

Required

Four keys must be present. recipient can be substituted with recipient_file.

OptionDefaultDescription
recipientrequiredTarget address. Or supply recipient_file= instead.
portrequiredSMTP port: 587, 465, or 25. Optional with eml_out_dir= (offline), where nothing connects.
threadsrequiredConcurrent worker threads.
messagesrequiredMessages per thread, not total. 0 means run forever until interrupted.
Minimum viable run
smtpbench [email protected] port=587 threads=5 messages=10

Load generation

How much traffic, and how fast.

OptionDefaultDescription
ratenoneWhole-run cap in messages per second, enforced by a token bucket shared across all threads. Mutually exclusive with delay and random_delay.
delay0Fixed pause in seconds between messages, per thread.
random_delayfalseRandom 1–15 second pause between messages, per thread.
Sustained load, capped at 10 msg/s across all threads, runs until stopped
smtpbench [email protected] port=587 threads=10 messages=0 rate=10
Gentle trickle: five threads, one message each per second
smtpbench [email protected] port=587 threads=5 messages=100 delay=1

Targeting and delivery

Where the mail goes, and what happens when a host is down.

OptionDefaultDescription
recipient_filenoneFile of recipient addresses, one per line. Requires lb_host= or eml_out_dir=.
recipient_file_orderrandomrandom or roundrobin.
lb_hostauto MX lookupSend to this host directly, skipping DNS.
client_hostnamesystem hostnameHostname announced in HELO/EHLO.
transaction_timeout20SMTP transaction timeout, in seconds.
max_retries3Retry attempts per message.
retry_delay20Seconds between retries.
Let DNS decide: walks MX records by priority, fails over automatically
smtpbench [email protected] port=25 threads=10 messages=100
Pin to one relay, skip DNS entirely
smtpbench [email protected] lb_host=smtp.dev.lets.qa port=587 threads=5 messages=10
Recipient and sender lists, round-robin for even coverage
smtpbench recipient_file=recipients.txt recipient_file_order=roundrobin \
          from_file=senders.txt \
          lb_host=smtp.dev.lets.qa port=587 threads=5 messages=100

Sender identity and journaling

Journal mode exists for testing archiving and compliance paths. The same message also goes to a journaling address, the way a real archiving deployment would capture it.

OptionDefaultDescription
from_addressno-reply@localhostSender address.
from_filenoneFile of From addresses.
from_file_orderrandomrandom or roundrobin.
journalfalseEnable journal mode: send a copy to a journaling address.
journal_addresssame as recipientWhere journal copies go.
journal_filenoneFile of journal addresses. Requires journal=true.
journal_file_orderrandomrandom or roundrobin.
Send a journal copy to an archive address
smtpbench [email protected] port=587 threads=5 messages=10 \
          journal=true [email protected]

Transport and authentication

OptionDefaultDescription
tls_modestarttls, or ssl on port 465starttls, ssl, or none.
use_tlsunsetDeprecated alias. true resolves to starttls, false to none. Prints a warning.
usernamenoneSMTP AUTH username. Discouraged on the command line.
passwordnoneSMTP AUTH password. Discouraged on the command line.
dotenv_pathauto-discoveredPath to a .env file for credentials.
Implicit TLS on 465
smtpbench [email protected] port=465 threads=5 messages=10 tls_mode=ssl
Authenticate via environment variables
export SMTPBENCH_USER=myuser
export SMTPBENCH_PASS=mypassword
smtpbench [email protected] port=587 threads=5 messages=10
Credentials from a specific .env file
smtpbench [email protected] port=587 threads=5 messages=10 \
          dotenv_path=/etc/smtpbench.env

Message realism

Real relays behave differently under 2MB attachments than under empty test messages.

OptionDefaultDescription
attachment_pathnoneAttach one specific file to every message.
attachment_sizenoneGenerate synthetic attachments. Fixed (512KB) or a range (10KB-2MB).
attachment_dirnoneSample attachments from a corpus directory of real files.
attachment_probability1.0Chance from 0.0 to 1.0 that a given message gets attachments.
attachment_count1Attachments per message. Accepts a range (1-3).
attachment_filenamesource or generated nameOverride the filename. With count above 1 becomes payload-1.bin, payload-2.bin, and so on.
attachment_mime_typeauto-detected, else application/octet-streamOverride the MIME type.
body_text_dirnonePrefix each body with a randomly chosen text file from this directory.
Realistic mixed traffic: 80% of messages get 1–3 files from a corpus
smtpbench [email protected] port=587 threads=5 messages=20 \
          attachment_dir=./corpus attachment_probability=0.8 attachment_count=1-3
Synthetic variable-size payloads, no corpus needed
smtpbench [email protected] port=587 threads=5 messages=50 \
          attachment_size=10KB-2MB
Vary the body text too
smtpbench [email protected] port=587 threads=5 messages=20 \
          body_text_dir=./text-corpus

Output and observability

OptionDefaultDescription
logfile_output./logsDirectory for log files.
debugfalseVerbose debug logging, including SMTP protocol detail.

Every message is traceable

Each message carries headers for correlation across distributed mail systems. The run UUID is constant per run; thread and message IDs vary per message. Find one message in a downstream mailbox and tie it back to the log line that produced it, or filter an entire run out of a shared mailbox afterwards.

X-SMTPBench-Run-UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
X-SMTPBench-Thread-ID: 3
X-SMTPBench-Message-ID: 7

The summary artifact

Every run writes summary_{timestamp}_{uuid}.json. Field shapes below; the file also records elapsed_seconds and config.address_lists.

{
  "run_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "config": {
    "threads": 5, "messages": 100, "rate": null,
    "tls_mode": "starttls", "auth": false, "port": 587, "offline": false
  },
  "totals": { "sent": ..., "failed": ..., "retried": ..., "success_rate": ... },
  "latency_ms": { "p50": ..., "p95": ..., "p99": ..., "max": ... },
  "per_mx": { "mx1.dev.lets.qa": { "sent": ..., "failed": ... } }
}

Offline mode

Fully offline: no DNS or MX lookup, no banner check, no connection. The recipient is used only as a message header, and since 1.2.1 no port is needed.

OptionDefaultDescription
eml_out_dirnoneWrite each composed message to this directory as {sha256}.eml instead of sending.
Compose 20 messages to disk, send nothing
smtpbench [email protected] threads=5 messages=20 \
          eml_out_dir=./eml-output
Full realism, still offline
smtpbench [email protected] threads=2 messages=10 \
          eml_out_dir=./eml-output attachment_dir=./corpus \
          attachment_probability=0.5 body_text_dir=./text-corpus

Getting help

--help, -h, help, ?, --version, -v, version, and zero arguments all short-circuit before any parsing, so they are always safe to run.

smtpbench --help      # full option list and examples
smtpbench --version