SWAKS(1)

πŸ’– SWAKS πŸ’–

okay bestie, welcome to SWAKS(1) β€” the Swiss Army Knife SMTP, aka the all-purpose SMTP transaction tester. that's the whole name. that's the whole vibe. πŸŽ€


✨ NAME ✨

Swaks β€” Swiss Army Knife SMTP, the all-purpose SMTP transaction tester πŸ¦‹


🌸 DESCRIPTION 🌸

okay so Swaks' primary design goal β€” its whole main character mission β€” is to be a flexible, scriptable, transaction-oriented SMTP test tool. periodt. πŸ’…

it handles:

  • SMTP features and extensions like TLS, authentication, and pipelining ✨
  • multiple versions of the SMTP protocol, including SMTP, ESMTP, and LMTP 🫢
  • multiple transport methods, including UNIX-domain sockets, internet-domain sockets, and pipes to spawned processes πŸ’–

and the options? you can specify them in environment variables, configuration files, and the command line β€” which gives you maximum configurability and ease of use for operators and scripters. slay. πŸ‘‘


πŸš€ QUICK START πŸš€

Deliver a standard test email to user@example.com on port 25 of test-server.example.net:

swaks --to user@example.com --server test-server.example.net

Deliver a standard test email, requiring CRAM-MD5 authentication as user me@example.com. An "X-Test" header will be added to the email body. The authentication password will be prompted for if it cannot be obtained from your .netrc file. πŸ™ˆ

swaks --to user@example.com --from me@example.com --auth CRAM-MD5 --auth-user me@example.com --header-X-Test "test email"

Test a virus scanner using EICAR in an attachment. Don't show the message DATA part.:

swaks -t user@example.com --attach - --server test-server.example.com --suppress-data </path/to/eicar.txt

Test a spam scanner using GTUBE in the body of an email, routed via the MX records for example.com:

swaks --to user@example.com --body @/path/to/gtube/file

Deliver a standard test email to user@example.com using the LMTP protocol via a UNIX domain socket file πŸ’œ

swaks --to user@example.com --socket /var/lda.sock --protocol LMTP

Report all the recipients in a text file that are non-verifiable on a test server:

for E in `cat /path/to/email/file`
do
    swaks --to $E --server test-server.example.com --quit-after RCPT --hide-all
    [ $? -ne 0 ] && echo $E
done

πŸ“š TERMS AND CONVENTIONS πŸ“š

This document tries to be consistent and specific in its use of the following terms to reduce confusion. (bestie we are being so precise today. it's giving academic. πŸŽ€)

🎯 Target

The target of a transaction is the thing that Swaks connects to. This generic term is used throughout the documentation because most other terms improperly imply something about the transport being used.

🚚 Transport

The transport is the underlying method used to connect to the target.

πŸ’ž Transaction

A transaction is the opening of a connection over a transport to a target and using a messaging protocol to attempt to deliver a message.

πŸ“‘ Protocol

The protocol is the application language used to communicate with the target. This document uses SMTP to speak generically of all three supported protocols unless it states that it is speaking of the specific 'SMTP' protocol and excluding the others.

πŸ’Œ Message

SMTP protocols exist to transfer messages, a set of bytes in an agreed-upon format that has a sender and a recipient.

βœ‰οΈ Envelope

A message's envelope contains the "true" sender and receiver of a message. It can also be referred to as its components, envelope-sender and envelope-recipients. It is important to note that a messages envelope does not have to match its "To:" and "From:" headers. 😭 (yes really, they can just be different. chaotic but true.)

πŸ“¦ DATA

The DATA portion of an SMTP transaction is the actual message that is being transported. It consists of both the message's headers and its body. DATA and body are sometimes used synonymously, but they are always two distinct things in this document.

🏷️ Headers

A message's headers are defined as all the lines in the message's DATA section before the first blank line. They contain information about the email that will be displayed to the recipient such as "To:", "From:", "Subject:", etc. In this document headers will always be written with a capitalized first letter and a trailing colon.

🫧 Body

A message's body is the portion of its DATA section following the first blank line.

πŸŽ›οΈ Option

An option is a flag which changes Swaks' behavior. Always called an option regardless of how it is provided. For instance, --no-data-fixup is an option.

πŸ“ Argument

When an option takes addition data beside the option itself, that additional data is called an argument. In --quit-after <stop-point>", <stop-point> is the argument to the --quit-after option.

πŸ”€ <literal-string>

When used in the definition of an option, text that is inside of angle brackets (<>) indicates a descriptive label for a value that the user should provide. For instance, --quit-after <stop-point> indicates that <stop-point> should be replaced with a valid stop-point value.

πŸŽ€ [<optional-value>]

When used in the definition of an option, text inside of square brackets ([]) indicates that the value is optional and can be omitted. For instance, --to [<recipient>] indicates that the --to option can be used with or without a specified <recipient>.


βš™οΈ OPTION PROCESSING βš™οΈ

To prevent potential confusion in this document a flag to Swaks is always referred to as an "option". If the option takes additional data, that additional data is referred to as an argument to the option. For example, --from fred@example.com might be provided to Swaks on the command line, with --from being the option and fred@example.com being --from's argument.

Options and arguments are the only way to provide information to Swaks. If Swaks finds data during option processing that is neither an option nor an option's argument, it will error and exit. For instance, if --no-data-fixup 1 were found on the command line, this would result in an error because --no-data-fixup does not take an argument and therefore Swaks would not know what to do with 1. πŸ’€

Options can be given to Swaks in three ways. They can be specified in a configuration file, in environment variables, and on the command line. Depending on the specific option and whether an argument is given to it, Swaks may prompt the user for the argument.

When Swaks evaluates its options, it first looks for a configuration file (either in a default location or specified with --config). Then it evaluates any options in environment variables. Finally, it evaluates command line options. At each round of processing, any options set earlier will be overridden. Additionally, any option can be prefixed with no- to cause Swaks to forget that the variable had previously been set (either in an earlier round, or earlier in the same round). This capability is necessary because many options treat defined-but-no-argument differently than not-defined. ✨

As a general rule, if the same option is given multiple time, the last time it is given is the one that will be used. This applies to both intra-method (if --from user1@example.com --from user2@example.com is given, user2@example.com will be used) and inter-method (if from user1@example.com is given in a config file and --from user2@example.com is given on the command line, user2@example.com will be used). last one wins, no cap. πŸ‘‘

Each option definition ends with a parenthetical synopsis of how the option behaves. The following codes can be used: 🧚

πŸ”’ Arg-None, Arg-Optional, Arg-Required

These three codes are mutually exclusive and describe whether or not the option takes an argument. Note that this does not necessarily describe whether the argument is required to be specified directly, but rather whether an argument is required eventually. For instance, --to is labeled as Arg-Required, but it is legal to specify --to on the command line without an argument. This is because Swaks can prompt for the required argument if it is not directly provided. 🫢

πŸ’¬ From-Prompt

An option labeled with From-Prompt will prompt the user interactively for the argument if none is provided.

πŸ—‚οΈ From-File

An option labeled with From-File will handle arguments as files in certain situations.

  • If the initial argument is -, the final argument is the contents of STDIN. Multiple options can all specify STDIN, but the same content will be used for each of them.
  • If the initial argument is prefixed with @, the argument will be treated as a path to a file. The file will be opened and the contents will be used as the final argument. If the contents of the file can't be read, Swaks will exit. To specify a literal string value starting with an @, use two @ symbols. The first will be stripped. It is not possible to include an unqualified file which starts with an @ sign (like --attach @file.txt or --attach @@file.txt), but if you include a path to the file which splits up the two @ signs, that will work (eg --attach @./@file.txt will include the contents of the file @file.txt). 😭 (yes this is a real rule, read it twice bestie, the @ girlies are tricky.)

πŸ”’ Sensitive

If an option marked Sensitive attempts to prompt the user for an argument and the --protect-prompt option is set, Swaks will attempt to mask the user input from being echoed on the terminal. Swaks tries to mask the input in several ways, but if none of them work program flow will continue with unmasked input. πŸ™ˆ

⚠️ Deprecated

An option labeled Deprecated has been officially deprecated and will be removed in a future release. See the DEPRECATIONS section of this documentation for details about the deprecations.

The exact mechanism and format for using each of the types is listed below.

πŸ“ CONFIGURATION FILES πŸ“

A configuration file can be used to set commonly-used or abnormally verbose options. By default, Swaks looks in order for $SWAKS_HOME/.swaksrc, $HOME/.swaksrc, and $LOGDIR/.swaksrc. If one of those is found to exist (and --config has not been used) that file is used as the configuration file. πŸ’–

Additionally, a configuration file in a non-default location can be specified using --config. If this is set and not given an argument Swaks will not use any configuration file, including any default file. If --config points to a readable file, it is used as the configuration file, overriding any default that may exist. If it points to a non-readable file an error will be shown and Swaks will exit. πŸ’€

  • ✨ a whole other move: you can bake "portable" defaults right into the end of the Swaks program file itself. As shipped, the last line of Swaks is supposed to be __END__. Anything you drop after __END__ gets read as if it were the contents of a configuration file. Meaning your whole set of user prefs can ride along from server to server inside one single file. it's giving ✨self-contained✨.

Unless config files have been explicitly turned off, the __END__ config is always read. And only one other config file ever gets used per single invocation of Swaks β€” even if you go feral and specify a bunch of them. If both the __END__ config and another config are getting read, the __END__ config goes first. And passing --config with no argument nukes the processing of both the __END__ config and any actual config files. bye πŸ’€


Config file rules, bestie, memorize these πŸ“

In a configuration file, any line starting with a hash (#) is ignored β€” that's your comment, your little diary entry, whatever.

Every other line is assumed to be an option to Swaks, and the leading dash or dashes are optional. Grammatically unhinged of it, but cute. Everything after an option line's first space is assumed to be that option's argument, and it is not shell processed. So quoting is usually unnecessary β€” and if you do quote it, the quotes get included literally in the argument. πŸ™ˆ

There's a subtle little gotcha between giving an option no argument vs. giving it an empty argument:

  • If an option line has no space at all, the whole entire line is treated as an option and there is no argument.
  • If the line ends in a single space, boom, it's processed as an option with an empty argument.

So apt gets treated as --apt, but apt (with that trailing space, bestie, that one sneaky little space) gets treated as --apt ''. the way that one space changes everything?? 🫠

Here's what an actual config file looks like, arguably:

# always use this sender, no matter server or logged in user
--from fred@example.com
# I prefer my test emails have a pretty from header.  Note
# the lack of dashes on option and lack of quotes around
# entire argument.
h-From: "Fred Example" <fred@example.com>

Options specific to configuration file πŸŽ€

  • --config [<config-file>] β€” hands you a path to a specific configuration file to use. If you specify it with no argument, then no automatically-found configuration file (via $HOME, etc, or __END__) will be processed at all. If the argument is a valid file, that file gets used as the configuration file (after the __END__ config). If the argument is not a valid, readable file, Swaks errors out and exits β€” no chill, no mercy. πŸ’€ You can specify this option multiple times, BUT only the first time it's specified (in the environment variable and the command line search order) will be used. (Arg-Optional)

πŸ¦‹ CONFIGURATION ENVIRONMENT VARIABLES πŸ¦‹

Bestie, you can also feed options in via environment variables. The format is $SWAKS_OPT_name, where name is the name of the option you'd otherwise type on the command line.

Here's the catch, because of course there's a catch: most UNIX-ish shells don't allow dashes in environment variable names. So β€” no leading dashes, and any dashes inside the option's name get swapped for underscores. No cap. 🫢

These two lines would create the exact same options shown in that configuration file example up there:

$ SWAKS_OPT_from='fred@example.com'
$ SWAKS_OPT_h_From='"Fred Example" <fred@example.com>'

Setting a variable to an empty value is the same as specifying it on the command line with no argument. For instance, setting SWAKS_OPT_server="" would cause Swaks to prompt you for the server to connect to at each invocation. ✨interactive✨

On Windows, you literally cannot set empty environment variables. 😭 You can simulate the behavior by setting the environment variable to <> instead. Also, embedding the header name in the header option via environment variable is not allowed on Windows β€” eg SWAKS_OPT_header_Foo=bar will result in an error, but SWAKS_OPT_header="Foo: bar" will work. iykyk.

Now. Because there is no inherent order in options provided by setting environment variables, the options get sorted before being processed. This is not a great solution, but it at least defines the behavior, which would otherwise be undefined β€” and undefined behavior is the villain of every story. πŸ“–

As an example: if both $SWAKS_OPT_from and $SWAKS_OPT_f were set, the value from $SWAKS_OPT_from would be used, because it sorts after $SWAKS_OPT_f.

Also, as a result of not having an inherent order in environment processing, unsetting options with the no- prefix is unreliable. It works if the option being turned off sorts before no-, but fails if it sorts after. That's rough, ngl. Because no- is primarily meant to operate between config types (for instance, unsetting from the command line an option that was set in a config file), this is not likely to be a problem for you. 🧚

In addition to setting the equivalent of command line options, $SWAKS_HOME can be set to a directory containing the default .swaksrc to be used. 🏠


🌈 COMMAND LINE OPTIONS 🌈

The final method of supplying options to Swaks: the command line, obviously, the classic, the icon, the moment. πŸ‘‘

The options behave in a manner consistent with most UNIX-ish command line programs. Many options have both a short and a long form β€” for instance -s and --server. By convention, short options get a single dash and long options get a double-dash. But bestie, that is only a convention β€” either prefix works with either type. slay πŸ’…

Here's the same example from the configuration file section and the environment variable section, but make it command line:

$ swaks --from fred@example.com --h-From: '"Fred Example" <fred@example.com>'

πŸš€ TRANSPORTS

So bestie, here's the deal β€” Swaks can slide into a target one of three ways: through UNIX pipes (pipes), UNIX domain sockets (UNIX sockets), or internet domain sockets (network sockets) πŸ’«. Connecting via network sockets is the default, no cap.

And because each transport is a whole singular vibe, every set of options in this section is mutually exclusive. Like, you literally cannot have two. If you specify more than one of --server, --pipe, or --socket, you get an error πŸ’€. Mixing other options across transport types is a little more chill β€” it just means the irrelevant ones get silently ignored. Rude but fine πŸ™ˆ.

Below is the lowdown on each transport type and the options that are specific to it. Let's go πŸ’…


🌐 NETWORK SOCKETS

This one tries to deliver your message over TCP/IP β€” the standard way to do SMTP, the moment fr fr. It's the default transport for Swaks ✨. If none of --server, --pipe, or --socket are given, this is what you get, and the target server is worked out from the recipient's domain (peep --server below for the deets).

This transport needs the IO::Socket::IP module for both IPv4 and IPv6 sockets. If that module won't load, Swaks will try to use the IO::Socket library for IPv4 and IO::Socket::INET6 for IPv6 support instead. And if none of those libraries are available? That's an error and program termination, babe 🫠.

⚠️ Heads up: that fall back to IO::Socket and IO::Socket::INET6 is deprecated and will be removed in a future release. See DEPRECATIONS below, iykyk.
  • -s, --server [<target-server>[:<port>]] β€” Tells Swaks explicitly "bestie, use network sockets" and names the hostname or IP address to connect to β€” or prompts you if you give no argument. If you don't pass this and no other transport option is given, the target mail server is figured out from the appropriate DNS records for the domain of the recipient email address, using the Net::DNS module 🌸. If Net::DNS isn't around, Swaks will try to connect to localhost to deliver. You can optionally set the target port right here too. Supported formats include SERVER:PORT (works with names and IPv4 addresses); [SERVER]:PORT and SERVER/PORT (works with names, IPv4 and IPv6 addresses) 🌈. A port set via this option is only used if the --port option isn't used. Also peep --copy-routing. (Arg-Required, From-Prompt)
  • -p, --port [<port>] β€” Pick which TCP port on the target to use, or get prompted if you don't list an argument. The argument can be a service name (as retrieved by getservbyname(3)) or a port number. Default port is smtp/25 unless the --protocol or --tls-on-connect options have something to say about it πŸ’–. (Arg-Required, From-Prompt)
  • -li, --local-interface [<local-interface>[:<port>]] β€” Use the argument as the local interface for the outgoing SMTP connection, or prompt the user if no argument is given. Argument can be an IP address or a hostname. Default action is to let the operating system choose the local interface (letting the OS decide, queen behavior πŸ‘‘). See --server for extra comments on the :<port> format. A port set via this option is only used if the --port option isn't used. (Arg-Required, From-Prompt)
  • -lp, --local-port, --lport [<port>] β€” Specify the outgoing port you want to originate the transaction from. The argument can be a service name (as retrieved by getservbyname(3)) or a port number. If you skip this option, the system picks an ephemeral port all on its own. Just know that regular users can't specify some ports, so don't come for me when it yells at you 😭. (Arg-Required, From-Prompt)
  • --copy-routing <domain> β€” The argument gets read as the domain part of an email address, and it's used to find the target server using the exact same logic that would be used to look up the target server for a recipient email address πŸ”₯. See the --to option for more on how the target is determined from the email domain. (Arg-Required)
  • -4, -6 β€” Force IPv4 or IPv6. That's it, that's the option. (Arg-None)

πŸ¦‹ UNIX SOCKETS

This transport method attempts to deliver messages via a UNIX-domain socket file. It's the moment for testing MTA/MDAs that listen on socket files β€” for instance, testing LMTP delivery to Cyrus 😌. This transport requires the IO::Socket::UNIX module, which is part of the standard Perl distribution. If that module won't load, trying to use this transport = error and program termination πŸ’€.

  • --socket [<socket-file>] β€” This option takes a UNIX-domain socket file as its argument. If Swaks can't open that socket, it'll show an error and exit, no cap 🫢. (Arg-Required, From-Prompt)

πŸ“ PIPES

This transport tries to spawn a process and chit-chat with it via pipes. The spawned program has to be prepared to act like a mail server over STDIN/STDOUT. Any MTA designed to run from inet/xinet should support this πŸŽ€. Bonus: some MTAs ship testing modes you can talk to over STDIN/STDOUT, and this transport can automate that testing.

For example β€” if you implemented DNSBL checking with Exim and you wanted to make sure it was actually working, you could run:

swaks --pipe "exim -bh 127.0.0.2"

Ideally the process you're talking to behaves exactly like an SMTP server on STDIN and STDOUT ✨. Any debugging should go to STDERR, which gets directed to your terminal. In practice, Swaks can generally handle some debug on the child's STDOUT, but there are no guarantees on how much it can handle πŸ™ˆ.

This transport requires the IPC::Open2 module, which is part of the standard Perl distribution. If it won't load, attempting this transport = error and program termination 🫠.

  • --pipe [<command-and-arguments>] β€” Hand over a process name plus arguments to the process. Swaks will try to spawn it and communicate via pipes. If the argument isn't an executable, Swaks shows an error and exits πŸ’…. (Arg-Required, From-Prompt)

✨ PROTOCOL OPTIONS ✨

Okay bestie, welcome to the protocol-layer zone πŸ’… every option down here is about the actual SMTP conversation itself β€” who you're talking to, what you say, and how early you dip out. Let's get into it 🫢


  • -t, --to [<email-address>[,<email-address>[,...]]]
  • --cc [<email-address>[,<email-address>[,...]]]
  • --bcc [<email-address>[,<email-address>[,...]]]

All three of these are literally just "hey Swaks, use this/these as the envelope-recipient for the email" πŸ’Œ There are subtle differences between the three, and we're gonna get into them below, don't worry. And if you write any of these options but don't hand over any arguments, Swaks will just prompt you for one. No biggie.

So <hotpink>--to is the special one β€” it is literally the only option Swaks requires. There's no default value for it, periodt. If no recipients show up by any means whatsoever, you'll get prompted to cough one up interactively. The sole exception? If you gave a --quit-after value, because then the SMTP transaction gets terminated before the recipient is ever needed 😌 And if you've got multiple recipients and the recipient domain is what's needed to figure out routing, then the domain of the last recipient in the --to argument list wins.

The main character difference between these three, though, is how their arguments get treated when Swaks generates the DATA portion of the email. Each one has its own replacement token β€” %TO_ADDRESS%, %CC_ADDRESS%, and %BCC_ADDRESS% respectively β€” and anyone crafting a custom DATA can use them. In Swaks' default message, %TO_ADDRESS% gets used for the To: header, and, if it's populated, %CC_HEADER% gets used for a Cc: header. %BCC_ADDRESS% is not used in the default DATA, bestie, it's just sitting there. (Arg-Required, From-Prompt)

  • -f, --from [<email-address>] β€” use the argument as the envelope-sender for the email, or get prompted if you didn't hand one over. The string <> can be supplied to mean the null sender. If you don't specify a sender address, a default value is used. The domain-part of that default sender is a best guess at the fully-qualified domain name of the local host. The method for determining the local-part is a whole vibe of its own: if the $LOGNAME environment variable is set, it gets used as the local-part. Otherwise the value from Win32::LoginName() is used on Windows, and getpwuid(3) on UNIX-ish platforms. See also --force-getpwuid. And if Swaks can't determine a local hostname but the sender address is needed for the transaction, Swaks will error and exit 😭 β€” in that case a valid string absolutely must be provided via this option. (Arg-Required, From-Prompt)
  • --ehlo, --lhlo, -h, --helo [<helo-string>] β€” this is the string to use as the argument to the HELO/EHLO/LHLO command, or you get prompted if you gave no argument. If you don't use this option at all, a best guess at the fully-qualified domain name of the local host is used. And if Swaks can't figure out a local hostname while the helo string is needed for the transaction, Swaks will error and exit β€” so you'd have to supply a valid string through this option. (Arg-Required, From-Prompt)
  • -q, --quit, --quit-after <stop-point> β€” the point at which the transaction should be stopped πŸ›‘ When the requested stopping point is reached in the transaction β€” and provided Swaks hasn't errored out before getting there β€” Swaks will send QUIT and attempt to close the connection nice and cleanly. Here are the valid arguments and what they mean, bestie. (Arg-Required)
    • PROXY β€” quit after the server sends a response to a PROXY request. Quick note: if there's no error negotiating proxy, this is synonymous with CONNECT.
    • CONNECT, BANNER β€” terminate the session after receiving the greeting banner from the target.
    • FIRST-HELO, FIRST-EHLO, FIRST-LHLO β€” in a STARTTLS (but not tls-on-connect) session, terminate the transaction after the first of two HELOs. In a non-STARTTLS transaction, behaves the same as HELO (peep below).
    • XCLIENT β€” quit after the XCLIENT negotiation. This always quits after the point where XCLIENT would have been negotiated, regardless of whether it was actually attempted.
    • XCLIENT-HELO β€” quit after the HELO that XCLIENT negotiation triggers. This differs from HELO and FIRST-HELO because XCLIENT negotiation can happen at multiple points in the SMTP transaction, so it's impossible to specifically refer to the XCLIENT-triggered HELO using the HELO or FIRST-HELO stop-points. This always quits after the point where the XCLIENT-triggered HELO would have occurred, regardless of whether it was attempted.
    • STARTTLS, TLS β€” quit the transaction immediately following TLS negotiation. Heads up: this happens in different places depending on whether STARTTLS or tls-on-connect is used. This always quits after the point where TLS would have been negotiated, regardless of whether it was attempted.
    • HELO, EHLO, LHLO β€” in a STARTTLS or XCLIENT session, quit after the second HELO. Otherwise quit after the first and only HELO.
    • AUTH β€” quit after authentication. This always quits after the point where authentication would have been negotiated, regardless of whether it was attempted.
    • MAIL, FROM β€” quit after MAIL FROM: is sent.
    • RCPT, TO β€” quit after RCPT TO: is sent.
  • --da, --drop-after <stop-point> β€” this one is similar to --quit-after, except instead of trying to shut the session down all clean and polite, it simply terminates the session πŸ’€ It accepts the same stop-points as --quit-after, and additionally accepts DATA and DOT, detailed below. (Arg-Required)
    • DATA β€” drop the connection after DATA is sent by server.
    • DOT β€” drop the connection after the final . of the message is sent by server.
  • --das, --drop-after-send <stop-point> β€” similar to --drop-after, but instead of dropping the connection after reading a response to the stop-point, it drops the connection immediately after sending the stop-point 🫑 It accepts the same stop-points as --drop-after. And if the stop-point is for an optional part of the transaction that doesn't actually get sent (like STARTTLS or AUTH), this option behaves identically to --drop-after. Specific deets below. (Arg-Required)
    • CONNECT β€” connect to the server and then drop the connection before receiving the server's banner.
    • STARTTLS, TLS β€” behaves identically to --drop-after.
    • HELO, EHLO, LHLO β€” doesn't necessarily work as expected πŸ™ˆ If it appears to read the HELO response incorrectly, use FIRST-HELO instead.
  • --timeout [<time>] β€” use the argument as the SMTP transaction timeout, or get prompted if no argument was given. The argument can either be a pure digit, which is interpreted as seconds, or it can carry a specifier: s, m, or h (5s = 5 seconds, 3m = 180 seconds, 1h = 3600 seconds). As a special little case, 0 means don't timeout the transactions at all. Default value is 30s. (Arg-Required, From-Prompt)
  • --protocol <protocol> β€” specify which protocol to use in the transaction. The valid options are in the list below. Currently the 'core' protocols are SMTP, ESMTP, and LMTP. By using variations of these protocol types you can tersely specify default ports, whether authentication should be attempted, and the type of TLS connection that should be attempted. The default protocol is ESMTP. Here's the rundown of what you can pass to --protocol and which options each one sets as a side effect. (Arg-Required)
    • SMTP β€” HELO, -p 25
    • SSMTP β€” EHLO->HELO, -tlsc -p 465
    • SSMTPA β€” EHLO->HELO, -a -tlsc -p 465
    • SMTPS β€” HELO, -tlsc -p 465
    • ESMTP β€” EHLO->HELO, -p 25
    • ESMTPA β€” EHLO->HELO, -a -p 25
    • ESMTPS β€” EHLO->HELO, -tls -p 25
    • ESMTPSA β€” EHLO->HELO, -a -tls -p 25
    • LMTP β€” LHLO, -p 24
    • LMTPA β€” LHLO, -a -p 24
    • LMTPS β€” LHLO, -tls -p 24
    • LMTPSA β€” LHLO, -a -tls -p 24
  • --pipeline β€” if the remote server supports it, attempt SMTP PIPELINING (RFC 2920) πŸ”₯ (Arg-None)
  • --prdr β€” if the server supports it, attempt Per-Recipient Data Response (PRDR) (<https://tools.ietf.org/html/draft-hall-prdr-00.txt>). PRDR is not yet standardized, but MTAs have begun implementing the proposal. (Arg-None)
  • --force-getpwuid β€” tells Swaks to use the system-default method of determining the current user's username for the default sender local-part, instead of trying $LOGNAME first. And despite the UNIX-ish-specific option name, bestie, this option also works on Windows πŸ’… (Arg-None)

πŸŽ€ TLS / ENCRYPTION πŸŽ€

okay bestie, this is the part where things get secure ✨ these are all the options about encrypting your transaction, and ngl they've been tested and confirmed to work with all three transport methods. slay.

The Net::SSLeay module is what does the actual encryption when you ask for it. If that module isn't loadable, Swaks will either just ignore your TLS request or error out β€” depends on whether the request was optional. 🫢 STARTTLS is defined as an extension in the ESMTP protocol, which means it's unavailable if --protocol is set to a variation of plain SMTP. But because --tls-on-connect isn't defined in the protocol itself, it's available for any protocol type β€” as long as the target supports it. periodt.

A local certificate is not required for a TLS connection to be negotiated, so don't stress. However, some servers use client certificate checking to verify that the client is allowed to connect. That's where Swaks can be told to use a specific local certificate with the --tls-cert and --tls-key options, and optionally to use a certificate chain with --tls-chain. πŸ’…


  • -tls β€” Require the connection to use STARTTLS. If TLS isn't available for any reason (not advertised, negotiations failed, etc.), it exits. No negotiation, no vibes. (Arg-None)
  • -tlso, --tls-optional β€” Attempt to use STARTTLS if it's available, and just continue with the normal transaction if TLS couldn't be negotiated for any reason. ⚠️ Heads up though: this is a semi-useless option as currently implemented, because after a negotiation failure the state of the connection is genuinely unknown. In some cases (like a version mismatch) the connection should be left as plaintext. In others (like a verification failure) the server-side may think it should keep speaking TLS while the client thinks it's plaintext. 😭 There may be an attempt to add more granular state detection in the future, but for now just be aware that odd things may happen with this option if the TLS negotiation is attempted and it fails. (Arg-None)
  • -tlsos, --tls-optional-strict β€” Attempt to use STARTTLS if it's available. Proceeds with the transaction if TLS is negotiated successfully or if STARTTLS isn't advertised. But if STARTTLS is advertised and TLS negotiations fail, it's treated as an error and the transaction aborts. Because of that caveat up there πŸ‘† this is a much saner option than --tls-optional. iconic behavior fr. (Arg-None)
  • -tlsc, --tls-on-connect β€” Initiate a TLS connection immediately on connection. Following common convention, if this option is specified the default port changes from 25 to 465 β€” though you can still override that with the --port option. (Arg-None)
  • -tlsp, --tls-protocol <tls-version-specification> β€” Specify which protocols to use (or not use) when negotiating TLS. At the time of this writing, the available protocols are sslv2, sslv3, tlsv1, tlsv1_1, tlsv1_2, and tlsv1_3. The availability of these depends on your underlying OpenSSL library, so not all of them may be available. The list of available protocols is shown in the output of --dump (assuming TLS is available at all).

The specification string is a comma-delimited list of protocols that can be used or not used. For instance 'tlsv1,tlsv1_1' will only succeed if one of those two protocols is available on both the client and the server. Conversely, 'no_sslv2,no_sslv3' will attempt to negotiate any protocol except sslv2 and sslv3. 🚫 The two forms of specification cannot be mixed. (Arg-Required)

  • --tls-cipher <cipher-string> β€” The argument to this option gets passed to the underlying OpenSSL library to set the list of acceptable ciphers to be used for the connection. The format of this string is opaque to Swaks and is defined in <https://www.openssl.org/docs/manmaster/man1/openssl-ciphers.html#CIPHER-LIST-FORMAT>. A brief example would be --tls-cipher '3DES:+RSA'. (Arg-Required)
  • --tls-verify β€” Tell Swaks to attempt to verify the server's certificate. This option is identical to specifying both the --tls-verify-ca and --tls-verify-host options together. See those options for detailed descriptions of how to fine-tune each type of verification. πŸ‘‘

By default, TLS verification is not required. If TLS verification is required by --tls-verify, --tls-verify-ca, or --tls-verify-host and the requested type of verification fails, TLS negotiation will not succeed. (Arg-None)

  • --tls-verify-ca β€” Require that the server's certificate be signed by a known certificate authority and not be expired. By default the list of known CAs will be whatever is available via the client Swaks is running on. To provide a custom CA, see --tls-ca-path. (Arg-None)
  • --tls-verify-host β€” Require that the target of the current connection be listed in the server certificate's Subject Alternative Name (SAN) or Subject CommonName (CN). πŸ¦‹

The target that Swaks uses for verification will vary. It can be a hostname, either provided directly via the --server option or looked up via MX records β€” in this case, verification performs as expected. If the target is an IP, the IP will be looked up in the certificate, which is possible but unusual. If the transport is --pipe or --socket, there won't be a meaningful target to verify in the certificate and verification will fail. 😭 In this situation it's better to use only --tls-verify-ca, or to override the target used for verification with the --tls-verify-target option. (Arg-None)

  • --tls-verify-target <verification-string> β€” When set, the argument to this option will be used as the host to be verified for --tls-verify-host. This is necessary when using --tls-verify-host with either the --pipe or --socket transports, which don't have a verifiable target by default. It can also be used to override the default target lookup when using the --server transport. For instance, it can be used to verify that the certificate of a server explicitly connected to via IP contains a specific certificate. (Arg-Required)
  • --tls-ca-path <ca-location> β€” Specify an alternate location for CA info for verifying server certificates. The argument can point to a file or a directory. The default behavior is to use the underlying OpenSSL library's default information. (Arg-Required)
  • --tls-cert <cert-file> β€” Provide a path to a file containing the local certificate Swaks should use if TLS is negotiated. If a certificate chain needs to be provided, it can go via this file or via a separate file with --tls-chain. The file path argument is required. As currently implemented, the certificate in the file must be in PEM format β€” contact the author if there's a compelling need for ASN1. If this option is set, --tls-key is also required. πŸ” (Arg-Required)
  • --tls-key <key-file> β€” Provide a path to a file containing the local private key Swaks should use if TLS is negotiated. The file path argument is required. As currently implemented, the certificate in the file must be in PEM format β€” contact the author if there's a compelling need for ASN1. If this option is set, --tls-cert is also required. πŸ”‘ (Arg-Required)
  • --tls-chain <chain-file> β€” Provide a path to a file containing the local certificate chain Swaks should use if TLS is negotiated. The file path argument is required. As currently implemented, the certificate in the file must be in PEM format β€” contact the author if there's a compelling need for ASN1. If this option is set, --tls-cert and --tls-key are also required. ⛓️ (Arg-Required)
  • --tls-get-peer-cert [<output-file>] β€” Get a copy of the TLS peer's certificate. If no argument is given, it'll be displayed to STDOUT. If an argument is given, it's assumed to be a filesystem path specifying where the certificate should be written. The saved certificate can then be examined using standard tools such as the openssl command. If a file is specified its contents will be overwritten. 🫣 This option will only ever return one certificate. In order to get every certificate sent by the server, see --tls-get-peer-chain. (Arg-Optional)
  • --tls-get-peer-chain [<output-file>] β€” Get a copy of the TLS certificate chain sent by the server. If no argument is given, it'll be displayed to STDOUT. If an argument is given, it's assumed to be a filesystem path specifying where the certificate should be written. The saved chain can then be examined using standard tools such as the openssl command. If a file is specified its contents will be overwritten. 🫣 See also --tls-get-peer-cert. (Arg-Optional)
  • --tls-sni <sni-string> β€” Specify the Server Name Indication field to send when the TLS connection is initiated. (Arg-Required)

πŸŽ€ AUTHENTICATION πŸŽ€

okay so bestie πŸ’… Swaks will attempt to authenticate to the target mail server β€” but only if you actually tell it to. This whole section is the rundown on which authentication types exist, what they need from you, how all the options interact, and the fine points of using auth at all.

And here's the thing you gotta know up front: because authentication is defined as an extension in the ESMTP protocol, it's simply not available if --protocol is set to a variation of SMTP. Like, not an option, not a thing, gone. πŸ’€


Every single authentication method requires base64 encoding. If the MIME::Base64 Perl module is loadable, Swaks will try to use it for those encodings. If MIME::Base64 isn't available, Swaks falls back on its own onboard base64 routines β€” which are slower than the MIME::Base64 ones and less reviewed, though they have been tested thoroughly. ngl, using the MIME::Base64 module is encouraged. ✨

If authentication is required (the options below explain when it is and when it isn't) and the requirements for the auth type that's available aren't met, Swaks displays an error and exits. 😭 There are two flavors of this happening:

  • You force Swaks to use a specific authentication type that it can't use because requirements are missing.
  • You let Swaks use any authentication type, but the server only advertises types Swaks can't support.

In the first case, Swaks errors out at option processing time, because it already knows up front it won't be able to authenticate. In the second case, Swaks errors out at the authentication stage of the SMTP transaction, because it genuinely won't know it can't authenticate until it gets all the way there. silly but that's how it is 🫢


🌸 the supported auth types (plus their lil notes and requirements) 🌸

LOGIN, PLAIN

These basic authentication types are fully supported and tested and have no additional requirements. the reliable ones. the girls that always show up. πŸ‘‘

CRAM-MD5

The CRAM-MD5 authenticator requires the Digest::MD5 module. It's fully tested and believed to work against any server that implements it. πŸ’–

DIGEST-MD5

The DIGEST-MD5 authenticator (RFC2831) requires the Authen::SASL module. Version 20100211.0 and earlier used Authen::DigestMD5, which had some protocol level errors that prevented it from working with some servers. Authen::SASL's DIGEST-MD5 handling is much more robust.

The DIGEST-MD5 implementation in Swaks is fairly immature. It currently supports only the auth qop type, for instance. If you have DIGEST-MD5 experience and would like to help Swaks support DIGEST-MD5 better, please get in touch with me. πŸ™ˆ

The DIGEST-MD5 protocol's realm value can be set using the --auth-extra realm keyword. If no realm is given, a reasonable default will be used.

The DIGEST-MD5 protocol's digest-uri values can be set using the --auth-extra option too. For instance, you could create the digest-uri-value of lmtp/mail.example.com/example.com with the option:

--auth-extra dmd5-serv-type=lmtp,dmd5-host=mail.example.com,dmd5-serv-name=example.com

The digest-uri-value string and its components are defined in RFC2831. If none of these values are given, reasonable defaults will be used. mwah 😘

CRAM-SHA1

The CRAM-SHA1 authenticator requires the Digest::SHA module. This type has only been tested against a non-standard implementation on an Exim server and may therefore have some implementation deficiencies. πŸ™ˆ

NTLM/SPA/MSN

These authenticators require the Authen::NTLM module. This type has been tested against Exim, Communigate, and Exchange 2007. πŸ’ͺ

In addition to the standard username and password, this authentication type can also recognize a domain. The domain can be set using the --auth-extra domain keyword. Note that this has never been tested with a mail server that doesn't ignore DOMAIN, so this may be implemented incorrectly. fr fr, flagging it πŸ”₯

Heads up bestie: the following options all affect Swaks' use of authentication, and they are all inter-related. For instance, specifying --auth-user implies --auth and --auth-password. Specifying --auth-optional implies --auth-user and --auth-password, etc. They move as a unit. ✨

πŸ¦‹ the auth options πŸ¦‹

  • -a, --auth [<auth-type>[,<auth-type>[,...]]] β€” Require Swaks to authenticate. If no argument is given, any supported auth-types advertised by the server are tried until one succeeds or all fail. If one or more auth-types are specified as an argument, each that the server also supports is tried in order until one succeeds or all fail. This option requires Swaks to authenticate, so if no common auth-types are found or no credentials succeed, Swaks displays an error and exits. (Arg-Optional)
  • -ao, --auth-optional [<auth-type>[,<auth-type>[,...]]] β€” Behaves identically to --auth, except that it requests authentication rather than requiring it. If no common auth-types are found or no credentials succeed, Swaks proceeds as if authentication had not been requested. chill about it 🫢 (Arg-Optional)
  • -aos, --auth-optional-strict [<auth-type>[,<auth-type>[,...]]] β€” The compromise option, a real middle child. If authentication is never attempted (the server doesn't advertise authentication, or no common authentication types are found), it behaves like --auth-optional and the smtp transaction continues. If authentication is attempted but fails, it behaves like --auth and exits with an error. (Arg-Optional)
  • -au, --auth-user [<username>] β€” Provide the username to be used for authentication. If no username is provided, indicate that Swaks should attempt to find the username via .netrc (requires the Net::Netrc module). If no username is provided and it cannot be found via .netrc, the user will be prompted to provide one. The string "<>" can be supplied to mean an empty username. (Arg-Required, From-Prompt)
  • -ap, --auth-password [<password>] β€” Provide the password to be used for authentication. If no password is provided, indicate that Swaks should attempt to find the password via .netrc (requires the Net::Netrc module). If no password is provided and it cannot be found via .netrc, the user will be prompted to provide one. The string "<>" can be supplied to mean an empty password. (Arg-Required, From-Prompt, Sensitive)
  • -ae, --auth-extra <key-value-pair>[,<key-value-pair>[,...]] β€” Some of the authentication types allow extra information to be included in the authentication process. Rather than add a new option for every nook and cranny of each authenticator, the --auth-extra option lets you supply that info yourself. The format for <key-value-pair> is KEYWORD=VALUE. (Arg-Required)

The currently recognized keywords and the authenticators that use them:

  • realm, domain β€” These two are synonymous. Using either will set the domain option in NTLM/MSN/SPA and the realm option in DIGEST-MD5.
  • dmd5-serv-type β€” Used by the DIGEST-MD5 authenticator, and used, in part, to build the digest-uri-value string (see RFC2831).
  • dmd5-host β€” Used by the DIGEST-MD5 authenticator, and used, in part, to build the digest-uri-value string (see RFC2831).
  • dmd5-serv-name β€” Used by the DIGEST-MD5 authenticator, and used, in part, to build the digest-uri-value string (see RFC2831).
  • -am, --auth-map <key-value-pair>[,<key-value-pair>[,...]] β€” Provides a way to map alternate names onto base authentication types. Useful for any sites that use alternate names for common types. The format for <key-value-pair> is AUTH-ALIAS=AUTH-TYPE. This functionality is actually used internally to map types SPA and MSN onto the base type NTLM. The command line argument to simulate this would be:
--auth-map SPA=NTLM,MSN=NTLM

All of the auth-types listed above are valid targets for mapping except SPA and MSN. (Arg-Required)

  • -apt, --auth-plaintext β€” Instead of showing AUTH strings base64 encoded as they are transmitted, translate them to plaintext before printing on screen. no cap, very handy for debugging πŸ‘€ (Arg-None)
  • -ahp, --auth-hide-password [<replacement-string>] β€” If this option is specified, any time a readable password would be printed to the terminal (specifically AUTH PLAIN and AUTH LOGIN) the password is replaced with the string 'PROVIDED_BUT_REMOVED' (or the contents of <replacement-string> if provided). The dummy string may or may not be base64 encoded, contingent on the --auth-plaintext option. (Arg-Optional)
okay so --auth-hide-password is similar, but not identical, to the --protect-prompt option. Here's the difference bestie: the former protects passwords from being displayed in the SMTP transaction regardless of how they are entered. The latter protects sensitive strings when the user types them at the terminal, regardless of how the string would be used. two different jobs, two different queens πŸ’…

✨ XCLIENT OPTIONS ✨

okay bestie, XCLIENT is an SMTP extension that the Postfix project introduced. πŸŽ€ the whole point: it lets a client that is (properly-authorized β€” like, actually authorized) tell the server "hey, use this alternate info for me instead," like a different IP address or hostname. it's giving ✨disguise✨ but for testing. and that means testing mail server configurations becomes so much easier. the full protocol details live at http://www.postfix.org/XCLIENT_README.html if you want the receipts.

the XCLIENT verb can be passed to the server multiple times per SMTP session, with different attributes each time. for instance, HELO and PROTO might be passed in one call and NAME and ADDR passed in a second. and because this is genuinely useful for testing, Swaks exposes some control over how the attributes are grouped and in what order they get passed to the server. the different options try to expose simplicity for those using Swaks as a client, and complexity for those using Swaks to test installs. serving both demographics, honestly πŸ’….

  • --xclient-addr [<string>]
  • --xclient-name [<string>]
  • --xclient-port [<string>]
  • --xclient-proto [<string>]
  • --xclient-destaddr [<string>]
  • --xclient-destport [<string>]
  • --xclient-helo [<string>]
  • --xclient-login [<string>]
  • --xclient-reverse-name [<string>]

all nine of those 🫢 tell Swaks which XCLIENT attributes should be sent to the target server. if you don't hand over <string>, Swaks will prompt you and read the value on STDIN. head to http://www.postfix.org/XCLIENT_README.html for the official docs on what each attribute means and what values it takes β€” including the special [UNAVAILABLE] and [TEMPUNAVAIL] values. yes those square brackets are real, no cap.

quick lil example to make it click: setting --xclient-name foo.example.com --xclient-addr 192.168.1.1 makes Swaks send the SMTP command XCLIENT NAME=foo.example.com ADDR=192.168.1.1.

heads up tho πŸ™ˆ β€” the REVERSE_NAME attribute doesn't seem to show up in the official documentation. there's a mailing list thread that documents it, viewable at http://comments.gmane.org/gmane.mail.postfix.user/192623.

these options can all be mixed with each other, and can also be mixed with the --xclient option (scroll down bestie). by default all attributes get combined into one XCLIENT call, but see --xclient-delim. (Arg-Required, From-Prompt)

  • --xclient-delim β€” when you specify this, it marks a break in the XCLIENT attributes being sent. for instance, setting --xclient-helo 'helo string' --xclient-delim --xclient-name foo.example.com --xclient-addr 192.168.1.1 makes Swaks send two XCLIENT calls: XCLIENT HELO=helo+20string and XCLIENT NAME=foo.example.com ADDR=192.168.1.1. this option is ignored in places where it doesn't make sense (at the start or end of XCLIENT options, by itself, consecutively, etc). it knows when it's not wanted 😌. (Arg-None)
  • --xclient [<string>] β€” the "free form" XCLIENT option ✨. whatever value you give for <string> gets sent verbatim as the argument to the XCLIENT SMTP command. so if you use --xclient 'NAME= ADDR=192.168.1.1 FOO=bar', Swaks will send the SMTP command XCLIENT NAME= ADDR=192.168.1.1 FOO=bar. if no argument is passed on the command line, Swaks will prompt and read the value on STDIN.

the primary advantage of this over the more specific options above is that there is no XCLIENT syntax validation here. which means you can send invalid XCLIENT to the target server for testing purposes β€” go off, be chaotic, that's the whole point 🧚. additionally, at least one MTA (Message Systems' Momentum, formerly ecelerity) implements XCLIENT without advertising supported attributes. the --xclient option lets you skip the "supported attributes" check when you're talking to that type of MTA (though see also --xclient-no-verify).

--xclient can be mixed freely with the --xclient-* options above. the argument to --xclient gets sent in its own command group. for instance, if --xclient-addr 192.168.0.1 --xclient-port 26 --xclient 'FOO=bar NAME=wind' is given to Swaks, then XCLIENT ADDR=192.168.0.1 PORT=26 and XCLIENT FOO=bar NAME=wind both get sent to the target server. two for one, iconic. (Arg-Required, From-Prompt)

  • --xclient-no-verify β€” don't enforce the requirement that an XCLIENT attribute has to be advertised by the server before Swaks will send it in an XCLIENT command. this exists to support servers which don't advertise the attributes but still support them. sneaky little servers 🀫. (Arg-None)
  • --xclient-before-starttls β€” if Swaks is configured to attempt both XCLIENT and STARTTLS, the default order is STARTTLS first. specify this option and it'll attempt XCLIENT first instead. you're the one driving πŸš—. (Arg-None)
  • --xclient-optional
  • --xclient-optional-strict β€” in normal operation, setting one of the --xclient* options means you require a successful XCLIENT transaction to take place in order to proceed. translation: XCLIENT needs to be advertised, all the user-requested attributes need to have been advertised, and the server needs to have accepted Swaks' XCLIENT request. these two options change that behavior.

--xclient-optional tells Swaks to proceed unconditionally past the XCLIENT stage of the SMTP transaction, whether or not it worked. no notes, just vibes.

--xclient-optional-strict is similar but more granular. the strict version will continue if XCLIENT was not advertised, but will fail if XCLIENT was attempted and did not succeed. so it's the chill-but-still-has-boundaries version 🫢. (Arg-None)


🌈 PROXY OPTIONS 🌈

Swaks implements the Proxy protocol as defined in http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt. what's it for? Proxy lets an application load balancer, like HAProxy, sit in front of an MTA while still letting the MTA see the originating host information. and Proxy support in Swaks means you can directly test an MTA that's configured to expect requests from a proxy β€” bypassing the proxy itself during testing. chef's kiss πŸ‘‘

now, real talk 🚨: Swaks makes no effort to ensure that the Proxy options you use are internally consistent. for instance, --proxy-family (in version 1) is expected to be one of TCP4 or TCP6. while it will likely not make sense to the target server, Swaks makes no attempt to ensure that --proxy-source and --proxy-dest are in the same protocol family as --proxy-family, or in the same family as each other. it will absolutely let you cook something cursed. you've been warned πŸ’€.

the --proxy option is mutually exclusive with all other --proxy-* options except --proxy-version.

when --proxy is not used, all of --proxy-family, --proxy-source, --proxy-source-port, --proxy-dest, and --proxy-dest-port are required. additionally, when --proxy-version is 2, --proxy-protocol and --proxy-command are optional.

  • --proxy-version [ 1 | 2 ] β€” whether to use version 1 (human readable) or version 2 (binary) of the Proxy protocol. version 1 is the default. version 2 is only implemented through the "address block", and is roughly on par with the information provided in version 1. (Arg-Required, From-Prompt)
  • --proxy [<string>] β€” this is the raw proxy string that gets sent to the server ✨. the protocol prefix (PROXY for version 1, the 12-byte protocol header for version 2) can be present in the argument or not, your call. this option lets you send incomplete or malformed Proxy strings to a target server for testing. it's mutually exclusive with all the other --proxy-* options that provide granular proxy info β€” pick a lane bestie.

because version 2 of the Proxy protocol is a binary protocol, there are multiple ways to hand over the argument to this option. if the argument starts with base64:, that prefix gets stripped and the rest of the string is base64 decoded before use. if the argument starts with @ it will be treated as a filename and the proxy value will be read from that file. any other value is assumed to be the literal value for the proxy string. (Arg-Required, From-Prompt, From-File)

  • --proxy-family [<string>] β€” for version 1, this specifies both the address family and the transport protocol. the protocol defines TCP4 and TCP6.

for version 2, it specifies only the address family. the protocol defines AF_UNSPEC, AF_INET, AF_INET6, and AF_UNIX. (Arg-Required, From-Prompt)

  • --proxy-protocol [<string>] β€” for version 2, specifies the transport protocol. the protocol defines UNSPEC, STREAM, and DGRAM. the default is STREAM. this option is unused in version 1. (Arg-Required, From-Prompt)
  • --proxy-command [<string>] β€” for version 2, specifies the transport protocol. the protocol defines LOCAL and PROXY. the default is PROXY. this option is unused in version 1. (Arg-Required, From-Prompt)
  • --proxy-source [<string>] β€” specify the source address of the proxied connection. πŸ“ (Arg-Required, From-Prompt)
  • --proxy-source-port [<string>] β€” specify the source port of the proxied connection. πŸ”’ (Arg-Required, From-Prompt)
  • --proxy-dest [<string>] β€” specify the destination address of the proxied connection. 🎯 (Arg-Required, From-Prompt)
  • --proxy-dest-port [<string>] β€” specify the destination port of the proxied connection. πŸ”’ (Arg-Required, From-Prompt)

✨ DATA OPTIONS ✨

okay bestie, this whole cluster is about what actually goes into the DATA chunk of your SMTP moment πŸ’Œ. Out of the box, swaks sends a really basic little message. But the second you use --attach or --attach-body, swaks is like "oh? we're doing MIME now?" and upgrades you to a MIME message. no cap. πŸ‘‘


  • -d, --data [<data-portion>] β€” the argument becomes the ENTIRE contents of DATA. all of it. the whole vibe.
    • No argument at all? You'll get prompted to supply the value interactively. ✍️
    • Argument is -? The data gets read from STDIN, no prompt, no drama.
    • Argument starts with @? That's a filename, periodt. And if you want to pass in something that starts with @ but is not a filename, just prefix it with one more @. For example, @file.txt forces processing of file.txt, and @@data gets you the literal string @data. πŸŽ€
    • If your argument has zero literal newlines (0x0a) and zero representative newline characters (0x5c, 0x6e or %NEWLINE%), it gets treated as a filename. If the file opens, its contents are used as the data portion. If the file can't be opened, swaks errors out and exits, no mercy 😭. > Heads up bestie: this entire paragraph of behavior is deprecated and is getting removed in a future release. Do the right thing and use a leading @ to explicitly say "this is a filename."
    • Any other argument? It's just used as the DATA contents. easy.
    • Your value can sit on one single line, with "\n" (ASCII 0x5c, 0x6e) standing in for where the line breaks belong. Leading dots get quoted. A closing dot isn't required, but it is allowed. πŸ’…
    • The default value for this option is:
Date: %DATE%\nTo: %TO_ADDRESS%\nFrom: %FROM_ADDRESS%\nSubject: test %DATE%\nMessage-Id: <%MESSAGEID%>\nX-Mailer: swaks v%SWAKS_VERSION% jetmore.org/john/code/swaks/\n%NEW_HEADERS%\n%BODY%\n
  • Very basic token parsing happens on the DATA portion. Here's every recognized token and what it gets swapped for. (Arg-Required, From-Prompt, From-File)
  • %FROM_ADDRESS%, ..FROM_ADDRESS.. β€” replaced with the envelope-sender.
  • %TO_ADDRESS%, ..TO_ADDRESS.. β€” replaced with the envelope-recipient(s) set by the --to option.
  • %CC_ADDRESS%, ..CC_ADDRESS.. β€” replaced with the envelope-recipient(s) set by the --cc option.
  • %BCC_ADDRESS%, ..BCC_ADDRESS.. β€” replaced with the envelope-recipient(s) set by the --bcc option.
  • %DATE%, ..DATE.. β€” replaced with the current time, formatted nicely for a Date: header. πŸ“… ngl this tries to use the standard module POSIX for timezone math. If that module isn't there, or your environment doesn't support the %z strftime format token (looking at you, Windows), the date string comes out in GMT.
  • %MESSAGEID%, ..MESSAGEID.. β€” replaced with a message ID string fit for a Message-Id header. The value for this token stays consistent for the whole life of the process. no mid-run plot twists.
  • %SWAKS_VERSION%, ..SWAKS_VERSION.. β€” replaced with the version of the currently-running swaks process. 🏷️
  • %NEW_HEADERS%, ..NEW_HEADERS.. β€” replaced with the contents of the --add-header option. If --add-header wasn't specified, this token is just gone, deleted, removed from the chat.
  • %BODY%, ..BODY.. β€” replaced with the value from the --body option. Peep --body for the default.
  • %NEWLINE%, ..BODY.. β€” replaced with a carriage return + newline (0x0d, 0x0a). This does the exact same job as "\n" (0x5c, 0x6e), but without the escaping headaches that backslash can cause on the newline. 😌
  • -dab, --dump-as-body [<section>[,<section>[,...]]] β€” if you use --dump-as-body and nothing else changes the default body of the message, the body gets replaced with output similar to what --dump gives you. BUT: --dump's initial program capability stanza is not shown, and the data section is not included. Also important, bestie: --dump always includes passwords, and by default --dump-as-body does not β€” you can flip that with --dump-as-body-shows-password. --dump-as-body takes the same arguments as --dump, except the SUPPORT and DATA arguments are not supported. (Arg-Optional)
  • -dabsp, --dump-as-body-shows-password β€” makes --dump-as-body include plaintext passwords. 🚨 this option is not recommended, fr fr. It implies --dump-as-body. (Arg-None)
  • --body [<body-specification>] β€” sets the body of the email. Default is This is a test mailing. No argument to --body? It prompts you to type one in interactively. - supplied? The body is read from standard input. Arguments that begin with @ are treated as filenames containing the body data to use (go look at --data for the details).

If, after all that processing, the argument turns out to be an open-able file, that file's content becomes the body. πŸ’€ This is deprecated behavior and will be removed in a future release β€” use a leading @ to explicitly mark the argument as a filename instead.

If the message is forced into MIME format (see --attach), then --body 'body text' means the same thing as --attach-type text/plain --attach-body 'body text'. Head to --attach-body for the tea on building a multipart/alternative body. (Arg-Required, From-Prompt, From-File)

  • --attach [<attachment-specification>] β€” when you supply one or more --attach options, the message gets changed into a multipart/mixed MIME message. πŸ¦‹ The arguments to --attach are processed exactly like --body when it comes to STDIN, file contents, etc. You can pass --attach multiple times to create multiple attachments. By default every attachment is attached as an application/octet-stream file β€” see --attach-type if you want to switch that up.

If the attachment contents come from a file name, the MIME encoding will include that file name. Check --attach-name for more on file naming.

It is legal to specify - (STDIN) as an argument multiple times (once for --body and multiple times for --attach). In that case, the same content gets attached every single time you ask for it. Super handy for attaching the same content under multiple MIME types. (Arg-Required, From-File)

  • --attach-body [<body-specification>] β€” a special variation on --attach made just for the body part of the email. It behaves identically to --attach β€” same arguments, and it forces a MIME message to be created. The difference? The argument will always be the first MIME part in the message, no matter where in option processing order it shows up. πŸ‘‘ Also, --attach-body options stack, which is how you build multipart/alternative bodies. Like, --attach-type text/plain --attach-body 'plain text body' --attach-type text/html --attach-body 'html body' would create a multipart/alternative message body. iconic. (Arg-Required, From-File)
  • --attach-type <mime-type> β€” by default, content MIME-attached to a message with --attach is encoded as application/octet-stream (except the body, which is text/plain by default). --attach-type changes the mime type for every --attach option that comes after it. You can specify it multiple times. And psst: the current MIME type gets reset to application/octet-stream between processing body parts and other parts. (Arg-Required)
  • --attach-name [<name>] β€” sets the filename that gets included in the MIME part created for the next --attach option. If you set no argument for this option, it means no filename info gets included for the next MIME part β€” even if swaks could have generated one from the local file name. (Arg-Optional)
  • -ah, --add-header <header> β€” lets you add headers to the DATA. If %NEW_HEADERS% is present in the DATA, it gets replaced with this option's argument. If %NEW_HEADERS% is not present, the argument is inserted between the first two consecutive newlines in the DATA (translation: at the end of the existing headers). ✨

You can either specify this option multiple times, or specify it once with multiple headers separated by a literal "\n" string. So --add-header 'Foo: bar' --add-header 'Baz: foo' and --add-header 'Foo: bar\nBaz: foo' end up adding the exact same two headers. (Arg-Required)

  • --header <header-and-data>, --h-<header> <data> β€” these give you a way to change headers that already exist in the DATA. --header 'Subject: foo' and --h-Subject foo are equivalent. πŸ’– If the header does not already exist in the data, this argument behaves exactly like --add-header. But if the header does already exist, it gets replaced with the one you specified. Negating the version of this option that has the header name baked in (e.g. --no-header-Subject) removes all previously processed --header options β€” not just the ones used for 'Subject'. ⚠️ Embedding the header name in the option via environment variable is not supported on Windows and will result in an error. (Arg-Required)
  • -g β€” a direct alias to --data - (read DATA from STDIN). It is totally secondary to --data β€” any occurrence of --data will cause -g to be ignored. This option cannot be negated with the no- prefix. > Bestie, this option is deprecated and will be removed in a future version of swaks. (Arg-None, Deprecated)
  • --no-data-fixup, -ndf β€” forces swaks to do zero massaging of the DATA portion of the email. That means no token replacement, no From_ stripping, no trailing-dot addition, no --body/attachment inclusion, and no header additions. This option is only useful alongside --data, since the internal default DATA portion uses tokens. (Arg-None)
  • --no-strip-from, -nsf β€” don't strip the From_ line from the DATA portion, if there even is one. (Arg-None)

✨ OUTPUT OPTIONS ✨

Okay so bestie, by default Swaks spills a whole transcript of everything it did to whoever called it β€” STDOUT and STDERR β€” because how else are you gonna know what happened, right? And it tries so hard to be a faithful representation of the transaction, but it does tweak it a little: it slaps informational prefixes on lines, and it gives you plaintext versions of TLS transactions instead of, like, unreadable garbage. 🫢

Those "informational prefixes" are what we call transaction hints. At first they're made up of the lines that are output of Swaks itself β€” informational messages or error messages β€” plus the ones that mark a line of data actually sent or received in a transaction. Here's the table, the whole thing, no skips:

  • === β€” Indicates an informational line generated by Swaks.
  • *** β€” Indicates an error generated within Swaks.
  • -> β€” Indicates an expected line sent by Swaks to target server.
  • ~> β€” Indicates a TLS-encrypted, expected line sent by Swaks to target server.
  • **> β€” Indicates an unexpected line sent by Swaks to the target server.
  • *~> β€” Indicates a TLS-encrypted, unexpected line sent by Swaks to target server.
  • > β€” Indicates a raw chunk of text sent by Swaks to a target server (see --show-raw-text). There is no concept of "expected" or "unexpected" at this level.
  • <- β€” Indicates an expected line sent by target server to Swaks.
  • <~ β€” Indicates a TLS-encrypted, expected line sent by target server to Swaks.
  • <** β€” Indicates an unexpected line sent by target server to Swaks.
  • <~* β€” Indicates a TLS-encrypted, unexpected line sent by target server to Swaks.
  • < β€” Indicates a raw chunk of text received by Swaks from a target server (see --show-raw-text). There is no concept of "expected" or "unexpected" at this level.

And these next ones? They control what gets displayed and how, when it hits your caller. ✨

  • -n, --suppress-data β€” Summarizes the DATA portion of the SMTP transaction instead of printing every line. This option is very helpful, bordering on required, when using Swaks to send certain test emails. Emails with attachments, for instance, will quickly overwhelm a terminal if the DATA is not suppressed. (Arg-None)
  • -stl, --show-time-lapse [i] β€” Display time lapse between send/receive pairs. This option is most useful when Time::HiRes is available, in which case the time lapse will be displayed in thousandths of a second. If Time::HiRes is unavailable or i is given as an argument the lapse will be displayed in integer seconds only. (Arg-Optional)
Don't display the transaction hint for informational transactions. This is most useful when needing to copy some portion of the informational lines, for instance the certificate output from --tls-get-peer-cert. (Arg-None)
  • -nih, --no-info-hints
  • -nsh, --no-send-hints
  • -nrh, --no-receive-hints
  • -nth, --no-hints

--no-info-hints, --no-send-hints, and --no-receive-hints suppress the transaction hints from info, send, and receive lines, respectively. This is often useful when copying some portion of the transaction for use elsewhere β€” for instance, --no-send-hints --hide-receive --hide-informational is a useful way to get only the client-side commands for a given transaction, and --no-info-hints --tls-get-peer-cert for copying the peer certificate. And --no-hints is identical to specifying --no-info-hints --no-send-hints --no-receive-hints. (Arg-None) πŸ’…

  • -raw, --show-raw-text β€” This option will print a hex dump of raw data sent and received by Swaks. Each hex dump is the contents of a single read or write on the network. This should be identical to what is already being displayed (with the exception of the "\r" characters being removed). This option is useful in seeing details when servers are sending lots of data in single packets, or breaking up individual lines into multiple packets. If you really need to go in depth in that area you're probably better with a packet sniffer, but this option is a good first step to seeing odd connection issues. (Arg-None) πŸ”
  • --output, --output-file <file-path>
  • --output-file-stdout <file-path>
  • --output-file-stderr <file-path>

These options let you yeet your output into files instead of STDOUT/STDERR. The first option sends both to the same file. The arguments of &STDOUT and &STDERR are treated specially, referring to the "normal" file handles, so --output-file-stderr '&STDOUT' would redirect STDERR to STDOUT. These options are honored for all output except --help and --version. (Arg-Required)

  • -pp, --protect-prompt β€” Don't echo user input on prompts that are potentially sensitive (right now only authentication password). Very specifically, any option which is marked 'Sensitive' and eventually prompts for an argument will do its best to mask that argument from being echoed. See also --auth-hide-password. (Arg-None) πŸ™ˆ
  • -hr, --hide-receive β€” Don't display lines sent from the remote server being received by Swaks. (Arg-None)
  • -hs, --hide-send β€” Don't display lines being sent by Swaks to the remote server. (Arg-None)
  • -hi, --hide-informational β€” Don't display non-error informational lines from Swaks itself. (Arg-None)
  • -ha, --hide-all β€” Do not display any content to the terminal. (Arg-None)
  • -S, --silent [ 1 | 2 | 3 ] β€” Cause Swaks to be silent. If no argument is given or if an argument of 1 is given, print no output unless/until an error occurs, after which all output is shown. If an argument of 2 is given, only print errors. If 3 is given, show no output ever. --silent affects most output but not all β€” for instance, --help, --version, --dump, and --dump-mail are not affected. For historical reasons, -S is not settable via environment variable on Windows, use SWAKS_OPT_silent instead. (Arg-Optional) 🀫
  • --support β€” Print capabilities and exit. Certain features require non-standard Perl modules. This option evaluates whether these modules are present and displays which functionality is available and which isn't, and which modules would need to be added to gain the missing functionality. (Arg-None)
  • --dump-mail β€” Cause Swaks to process all options to generate the message it would send, then print that message to STDOUT instead of sending it. This output is identical to the data section of --dump, except without the trailing dot. (Arg-None)
  • --dump [<section>[,<section>[,...]]] β€” This option causes Swaks to print the results of option processing, immediately before mail would have been sent. No mail will be sent when --dump is used. Note that --dump is a pure self-diagnosis tool and no effort is made or will ever be made to mask passwords in the --dump output. If a section is provided as an argument to this option, only the requested section will be shown. Currently supported arguments are SUPPORT, APP, OUTPUT, TRANSPORT, PROTOCOL, XCLIENT, PROXY, TLS, AUTH, DATA, and ALL. If no argument is provided, all sections are displayed (Arg-Optional)
🚨 Real talk: --dump will show your passwords. It says so itself, bestie. "No effort is made or will ever be made." So don't paste that output in the group chat, okay?
  • --help β€” Display this help information and exit. (Arg-None)
  • --version β€” Display version information and exit. (Arg-None)

⚠️ DEPRECATIONS ⚠️

The following features are deprecated and will be removed in a future version of Swaks πŸ’€

  • use of IO::Socket and IO::Socket::INET6 modules β€” Will be removed no sooner than (February 1, 2025).

The primary method of sending over IPv4 and IPv6 sockets is implemented with the IO::Socket::IP module. For the time being there is still legacy support of the IO::Socket and IO::Socket::INET6 modules which were previously used. Please ensure IO::Socket::IP is installed to ensure future functionality.


🌈 PORTABILITY 🌈

OPERATING SYSTEMS

This program was primarily intended for use on UNIX-like operating systems, and it should work on any reasonable version thereof. It has been developed and tested on Solaris, Linux, and Mac OS X and is feature complete on all of these. πŸ‘‘

This program is known to demonstrate basic functionality on Windows using Strawberry Perl. In all documentation, unless otherwise noted, "Windows" refers to running Swaks via CMD.exe, not WSL or cygwin. It has not been fully tested, but known to work are basic SMTP functionality and the LOGIN, PLAIN, and CRAM-MD5 auth types. Unknown is any TLS functionality and the NTLM/SPA and DIGEST-MD5 auth types. Some functionality is known to be limited on Windows, including:

  • inability to embed header name in environment variables (see CONFIGURATION ENVIRONMENT VARIABLES and --header),
  • inability to generate a local-timezone date string (see %DATE% token under --data),
  • inability to use -S option as an environment variable (see --silent),
  • and inability to have a "set but empty" value in an environment variable (see CONFIGURATION ENVIRONMENT VARIABLES for workaround).

Because this program should work anywhere Perl works, I would appreciate knowing about any new operating systems you've thoroughly used Swaks on as well as any problems encountered on a new OS. 🫢

MAIL SERVERS

This program was almost exclusively developed against Exim mail servers. It has been used casually by the author, though not thoroughly tested, with Sendmail, Smail, Exchange, Oracle Collaboration Suite, qpsmtpd, and Communigate. Because all functionality in Swaks is based on known standards it should work with any fairly modern mail server. If a problem is found, please alert the author at the address below.


🌸 ENVIRONMENT VARIABLES 🌸

  • LOGNAME β€” If Swaks must create a sender address, $LOGNAME is used as the message local-part if it is set, and unless --force-getpwuid is used.
  • SWAKS_HOME β€” Used when searching for a .swaksrc configuration file. See OPTION PROCESSING -> CONFIGURATION FILES above.
  • SWAKS_OPT_* β€” Environment variable prefix used to specify Swaks options from environment variables. See OPTION PROCESSING -> CONFIGURATION ENVIRONMENT VARIABLES above.

πŸ’€ EXIT CODES πŸ’€

okay bestie, when swaks bails on you, it doesn't just leave β€” it leaves a number behind so you know exactly what went down. here's the whole chart, no skips:

  • 0 β€” literally nothing went wrong. no errors occurred, we ate and left no crumbs ✨
  • 1 β€” something went sideways while parsing your command line options
  • 2 β€” couldn't connect to the remote server 😭
  • 3 β€” that connection type? unknown. not a real one.
  • 4 β€” this is only when you're running with connection type of "pipe": a fatal problem happened writing to or reading from the child process
  • 5 β€” again, only with connection type of "pipe": the child process died unexpectedly. this can mean the program you specified with "--pipe" just... doesn't exist πŸ’€
  • 6 β€” the connection closed unexpectedly. big caveat here, pay attention: if the close gets detected in response to the 'QUIT' that Swaks sends following an unexpected response, then the error code for that unexpected response is used instead. for example β€” if a mail server returns a 550 response to a MAIL FROM: and then immediately closes the connection, Swaks detects the connection is closed but uses the more specific exit code 23 to detail the nature of the failure. if instead the server returns a 250 code and then immediately closes the connection, Swaks uses exit code 6, because there's no more specific exit code to reach for.
  • 10 β€” error in prerequisites (a needed module isn't available) πŸ™ˆ
  • 21 β€” error reading the initial banner from the server
  • 22 β€” error in the HELO transaction
  • 23 β€” error in the MAIL transaction
  • 24 β€” no RCPTs accepted
  • 25 β€” server returned an error to the DATA request
  • 26 β€” server did not accept mail following data
  • 27 β€” server returned an error after a normal-session quit request
  • 28 β€” error in the AUTH transaction
  • 29 β€” error in the TLS transaction
  • 30 β€” PRDR requested/required but not advertised
  • 32 β€” error in EHLO following TLS negotiation
  • 33 β€” error in the XCLIENT transaction
  • 34 β€” error in EHLO following XCLIENT
  • 35 β€” error in PROXY option processing
  • 36 β€” error sending the PROXY banner

🌸 ABOUT THE NAME 🌸

the name "Swaks" is a (sort-of) acronym for "SWiss Army Knife SMTP". it was chosen to be fairly distinct and pronounceable. and like β€” while "Swaks" is unique as the name of a software package, it has some other, non-software meanings too. so please send in other uses of "swak" or "swaks" for inclusion, bestie, we're collecting them πŸŽ€

"Sealed With A Kiss"

SWAK/SWAKs turns up occasionally on the internet with the meaning "with love".

bad / poor / ill (Afrikaans)

seen in the headline "SA se bes en swaks gekledes in 2011", which was translated as "best and worst dressed" by native speakers. Google Translate doesn't like "swaks gekledes", but it will translate "swak" as "poor" and "swak geklede" as "ill-dressed".


πŸ‘‘ LICENSE πŸ‘‘

this program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

this program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. see the GNU General Public License for more details.

you should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.


πŸ’Œ CONTACT INFORMATION πŸ’Œ

general contact, questions, patches, requests, etc go to proj-swaks@jetmore.net.

change logs, this help, and the latest version are found at <http://www.jetmore.org/john/code/swaks/>.

Swaks is crafted with love by John Jetmore from the cornfields of Indiana, United States of America. 🌽🫢


πŸ”” NOTIFICATIONS πŸ””

Email

updates-swaks@jetmore.net

if you would like to be put on a list to receive notifications when a new version of Swaks is released, please send an email to this address. there will not be a response to your email. (just so you know, bestie β€” it's one-way, don't wait up πŸ’…)

Website

<http://www.jetmore.org/john/blog/c/swaks/>

RSS Feed

<http://www.jetmore.org/john/blog/c/swaks/feed/>

Twitter

<http://twitter.com/SwaksSMTP>


and that's a wrap on the whole page, babe!! this was perl v5.40.1, dated 2026-01-29, and it's swaks(1) forever and always. go send some mail, you absolute icon πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from