#integration-tests #email #replace #sendmail #stores #command-line

bin+lib trapmail

A command-line replacement for sendmail that stores mail. Useful for integration tests.

6 releases (3 breaking)

0.4.0 Jan 15, 2020
0.3.1 Dec 10, 2019
0.2.1 Dec 9, 2019
0.1.1 Dec 9, 2019

#800 in Testing

MIT/Apache

20KB
271 lines

trapmail

trapmail is a sendmail replacement for unit- and integration-testing that captures incoming mail and stores it on the filesystem. See the documentation for details.


lib.rs:

trapmail: A sendmail replacement for integration testing

trapmail is a sendmail replacement for unit- and integration-testing that captures incoming mail and stores it on the filesystem. Test cases can inspect the "sent" mails.

Use case

trapmail is intended for black-box testing systems that use the systemwide sendmail instance to send emails. Example:

  1. trapmail is installed and either replaces sendmail on the test system/container, or the application being tested is configured to use trapmail as its sendmail binary.
  2. An integration test (written in Rust) triggers various processes that cause the application to send mail, which is collected inside TRAPMAIL_STORE.
  3. Having access to TRAPMAIL_STORE as well, the trapmail library can be used inside the integration test to check if mail was queued as expected.

CLI

trapmail's commandline aims to mimick the original sendmail arguments, commonly also implemented by other MTAs like Exim or Postfix.

When trapmail receives a message, it stores it along with metadata a JSON file in the directory named in the TRAPMAIL_STORE environment variable, falling back to /tmp if not found. Files are named trapmail_PPID_PID_TIMESTAMP.json, where PPID is the parent process' PID, PID trapmails PID at the time of the call and TIMESTAMP a microsecond accurate timestamp.

Command-line options

Currently, trapmail does not "support" all the same command-line options that sendmail supports (all options are ignored, but logged). If you run into issues due to an unsupported option, feel free to open a PR to get it added.

Example

$ trapmail --debug -i -t foo@bar
To: Santa Clause <santa@example.com>
From: Marc <marc@example.com>
Subject: Please remove me from the naughty list.

Example body.
^D
Mail written to "/tmp/trapmail_1575911147313470_5913_6299.json"

The resulting mail is (somewhat readable) JSON, but can also be dumped using the cli tool:

$ trapmail --dump /tmp/trapmail_1575911147313470_5913_6299.json
Mail sent on 2019-12-09 17:05:47.000313 UTC from PID 6299 (PPID 5913).
CliOptions {
    debug: true,
    ignore_dots: true,
    inline_recipients: true,
    addresses: [
        "foo@bar",
    ],
    dump: None,
}
To: Santa Claus <santa@example.com>
From: Marc <marc@example.com>
Subject: Please remove me from the naughty list.

Example body.

Concurrency

While trapmail avoids collisions between stored messages from different processes due to its naming scheme, it is important to remember that it has no way to access any data of the test itself (the PPID is from the application-under-tests's PID, not the test binary).

Providing different TRAPMAIL_STORE targets allows for namespacing the data, but it may not always be possible to ensure this variable is set per test on a closed system.

API

The trapmail crate comes with a command-line application as well as a library. The library can be used in tests and applications to access all data that trapmail writes.

A minimal example to read the contents of the current trapmail folder:

use trapmail::MailStore;

// Load mail from the default mail directory.
let store = MailStore::new();

for load_result in store.iter_mails().expect("could not open mail store") {
    let mail = load_result.expect("could not load mail from file");
    println!("{}", mail);
}

Dependencies

~6.5–9MB
~157K SLoC