Home | Send Feedback

Catch-all SMTP servers for development and testing

Published: August 09, 2019  •  java

While writing applications that deliver emails is often easy, testing is sometimes challenging during development and unit testing. First, your application needs to connect to an SMTP server. Then you need to make sure that you do not accidentally send thousands of emails during development to your clients in production. Workarounds, like changing all email addresses in the database to a test email address or adding code that checks if the application runs in development and then change the recipient address, might work, but you don't have to forget to apply these changes. When changing the recipient address, you also face the problem that you can't tell for sure if an email goes to the correct recipient.

Another problem is what if you want to work on your project but don't have access to the SMTP server. For example, when you work in the office, you can use the company SMTP server, but when you work at home, you might not have access to this server. Or you want to work in an environment where you don't have any network access at all (for example on a plane).

The solution to all these problems is to install a catch-all SMTP server locally on your development computer. These are tiny applications that you start from the command line and that implement the SMTP protocol. They listen for SMTP traffic and accept all messages for any email address and don't relay them. Instead, they store them locally in a file or in RAM and give you access to the inbox with a GUI.

With such a setup in place you can always send emails to the correct recipients, no change required in the database or in the code, and it always works even when you don't have access to a network. As long as you don't forget to change the SMTP server address in your application's configuration file. A locally installed catch-all SMTP does not prevent your application from delivering emails to a real SMTP server.

In this blog post we are going to look at a few catch-all server applications. They are all free and run on multiple platforms.

Email Demo

First, we need a simple application that delivers emails. For this purpose, we write a trivial Spring Boot application. Add the spring-boot-starter-mail dependency to the project's classpath, which automatically includes the necessary email libraries to your project.

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>

pom.xml

In a Spring-managed bean, we inject the JavaMailSender bean, which is responsible for creating emails and for sending them.

@Service
public class EmailService {

  private final JavaMailSender mailSender;

  private final String defaultSender;

  public EmailService(JavaMailSender mailSender, AppProperties appProperties) {
    this.mailSender = mailSender;
    this.defaultSender = appProperties.getDefaultEmailSender();
  }

  public void sendEmail() throws MessagingException {
    MimeMessage message = this.mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message);
    helper.setFrom(this.defaultSender);
    helper.setTo("developer@test.com");
    helper.setText("<h1>Hello World</h1>", true);
    helper.setSubject("Test Email");

    this.mailSender.send(message);
  }

EmailService.java

This demo application also configures a GET endpoint (/send) that calls the sendEmail() of our service. We can then trigger the email delivery by opening the URL http://localhost:8080/send in a browser or sending a request from the command line with curl or any other HTTP client.

@RestController
public class EmailController {

  private final EmailService emailService;

  public EmailController(EmailService emailService) {
    this.emailService = emailService;
  }

  @GetMapping("/send")
  public void send() throws MessagingException {
    this.emailService.sendEmail();
  }

}

EmailController.java

Lastly, we need to configure the address and port of the SMTP server in the application.properties file. Important here that we always point to the catch-all server.

spring.mail.host=localhost
spring.mail.port=2525
app.default-email-sender=no-reply@test.com

application.properties

FakeSMTP

That's the catch-all server I often use. It's quite an old software and hasn't seen an update for 3 years, but it still works perfectly fine. There is no pre-built package you can download, instead you have to download the source code and then build the package yourself.

git clone https://github.com/Nilhcem/FakeSMTP.git
cd FakeSMTP
mvn package
java -jar target/fakeSMTP-2.1-SNAPSHOT.jar

FakeSMTP opens a Swing window where you can change the listening port and start the SMTP server. The GUI automatically lists incoming emails. Double click opens the email in an email client if one is installed or opens it in a text editor. Each email is also stored in a text file in the received-emails subfolder inside the FakeSMTP folder.

FakeSMTP

fake-smtp-server

This is a Node package that you install globally

 npm install -g fake-smtp-server

To start the server, you issue the following command.

fake-smtp-server 

The server listens on port 1025 for incoming SMTP traffic and starts a web server on port 1080.

You may change both ports with the following options.

fake-smtp-server --smtp-port 2525 --http-port 9090

To see the emails, open a browser and go to the URL http://localhost:9090
The server also provides a JSON interface under the address http://localhost:9090/api/emails
You can filter emails with request parameters. See the project page for more information.

fake-smtp-server npm

fake-smtp-server

This project, with the same name as the previous NPM package, is written in Java and uses Spring Boot to start a web and an SMTP server.

To install it on your computer, go to the release page and download the latest version.

Start the server with

java -jar fake-smtp-server-1.2.2.jar

By default, the application listens on port 5025 for SMTP traffic, and it provides a web GUI on port 5080.

The ports are configurable. You find all the necessary information on the project page.

fake-smtp-server java list fake-smtp-server java detail

Inbucket

Inbucket is an application written in Go and Elm. Download the latest version from the download page. Binaries are available for macOS, Windows, Linux, and FreeBSD.

Start the server with the inbucket binary. Inbucket starts the SMTP server on port 2500 and the webserver on port 9000. Unlike all the other servers we tested so far Inbucket also implements the POP3 protocol and starts a POP3 server on port 1100.

Inbucket is configured via environment variables. See this page for more information: https://github.com/inbucket/inbucket/blob/master/doc/config.md

Alternatively, you can start Inbucket as a Docker container.

docker run -d --name inbucket -p 9000:9000 -p 2500:2500 -p 1100:1100 inbucket/inbucket

Inbucket not only provides a web GUI where you can see all the incoming emails, it also provides an HTTP Rest API to programmatically access the emails. See this wiki page for more information: https://github.com/inbucket/inbucket/wiki/REST-API

Inbucket list Inbucket detail

smtp4dev

smtp4dev is written in C# and Typescript and runs on Windows, Linux, and macOS. Download the latest release from the release page.

To start the server run the Rnwood.Smtp4dev.exe binary. The application starts the SMTP server on port 25 and the HTTP server on port 5000

To change the SMTP port either change the setting in the appsettings.json configuration file before you start the binary. Alternatively, change the port via command-line option.

Rnwood.Smtp4dev.exe --smtpport 2525

smtp4dev is also available as a Docker container

docker run -p 3000:80 -p 2525:25 rnwood/smtp4dev:linux-amd64-v3

smtp4dev

GreenMail

GreenMail is written in Java and is the only server on this list that does not only support SMTP and POP3 (like Inbucket) but also IMAP.

Go to the download page and download the standalone version.

To start all three servers issue the following command.

java -Dgreenmail.setup.test.all -jar greenmail-standalone-1.5.10.jar

The default ports are: smtp = 3025, smtps = 3465, pop3 = 3110, pop3s = 3995, imap = 3143, imaps = 3993
You can change ports and which service should be started with command-line options. The applications prints out all supported options when you run it without any arguments.

java -jar greenmail-standalone-1.5.10.jar

GreenMail automatically creates user accounts when it receives emails. Our demo application sends emails to developer@test.com, so GreenMail creates a user account with user name and password set to "developer@test.com"

GreenMail does not provide a GUI to check the incoming emails. Because GreenMail implements POP3 and IMAP, you can easily attach any email client and access the mailbox in this way.

GreenMail supports different deployment models. As a standalone Java application, as a WAR module, and as a JBoss service. You can start GreenMail also in a Docker container.

docker pull greenmail/standalone:1.5.10
docker run -t -i -p 3025:3025 -p 3110:3110 -p 3143:3143 -p 3465:3465 -p 3993:3993 -p 3995:3995 greenmail/standalone:1.5.10

GreenMail settings GreenMail email

Unit tests

All the applications above fit well into a development workflow. Start a server manually, send emails from your application, and manually check emails via provided GUI. For unit tests, we need something that is automatic and reproducible. You could write a script that starts a server, runs the unit tests and inside the unit tests check the emails with POP3 or IMAP or via a provided REST API.

When you write unit tests for a Java application, GreenMail provides a straightforward solution. You can add GreenMail as a test dependency to your application. Then you can programmatically start the server before the unit tests run. The GreenMail library also provides convenient access methods to the email inbox.

Let's see how this integration looks like in Junit 5 tests.

First, we add the GreenMail library to the test classpath of our project. Because we are going to use Junit 5 and Spring Boot already references the new javax mail packages I exclude these transitive dependencies from the GreenMail library.

    <dependency>
      <groupId>com.icegreen</groupId>
      <artifactId>greenmail</artifactId>
      <version>1.5.10</version>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
        </exclusion>
        <exclusion>
          <groupId>javax.activation</groupId>
          <artifactId>activation</artifactId>
        </exclusion>
        <exclusion>
          <groupId>com.sun.mail</groupId>
          <artifactId>javax.mail</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

pom.xml

Then we start the GreenMail server before we run the unit tests. In this example, we start the server once before all the test methods run. Make sure that the port of the SMTP server is also configured correctly in your application. In a Maven managed Spring Boot application, we can do this by creating an application.properties file just for the tests (src/test/resources/application.properties)

In the @AfterAll method, the test class shuts down the server. The method purgeEmailFromAllMailboxes() deletes all emails in the inbox. As an example, we do this here after each @Test method.

@SpringBootTest
class EmailApplicationTests {

  @Autowired
  private EmailService emailService;

  @Autowired
  private AppProperties appProperties;

  private static GreenMail greenMail;

  @BeforeAll
  public static void setupSMTP() {
    greenMail = new GreenMail(new ServerSetup(2525, "127.0.0.1", "smtp"));
    greenMail.start();
  }

  @AfterAll
  public static void tearDownSMTP() {
    greenMail.stop();
  }

  @AfterEach
  public void cleanup() throws FolderException {
    greenMail.purgeEmailFromAllMailboxes();
  }

EmailApplicationTests.java

In the @Test method, we then first call our service, which sends an email. This can take a few milliseconds, therefore, we block the calling thread with waitForIncomingEmail(). The argument we pass to this method determines how many emails have to arrive until the method unblocks. The method waits 5 seconds for the given number of emails to arrive. If they don't arrive, the method returns false, otherwise true.

After that, we access the emails with getReceivedMessages(). This method returns all messages, in all folders for all users, and returns an array of javax.mail.internet.MimeMessage instances. A MimeMessage instance contains the email header information and the body of the email.

  @Test
  void testSendEmail() throws MessagingException, IOException {
    this.emailService.sendEmail();

    boolean ok = greenMail.waitForIncomingEmail(1);
    // boolean ok = greenMail.waitForIncomingEmail(10_000, 1);

    if (ok) {
      MimeMessage testMessage = greenMail.getReceivedMessages()[0];
      assertThat(testMessage.getSubject()).isEqualTo("Test Email");
      assertThat(testMessage.getRecipients(RecipientType.TO)[0].toString())
          .isEqualTo("developer@test.com");
      assertThat(testMessage.getFrom()[0].toString())
          .isEqualTo(this.appProperties.getDefaultEmailSender());

      String emailContent = (String) testMessage.getContent();
      assertThat(emailContent.replaceAll("\\r\\n|\\r|\\n", ""))
          .isEqualTo("<h1>Hello World</h1>");
    }
    else {
      Assertions.fail("email not sent");
    }
  }

EmailApplicationTests.java

An alternative to getReceivedMessages() is getReceivedMessagesForDomain() which only returns emails for a particular domain.

greenMail.getReceivedMessagesForDomain("test.com")

As mentioned before waitForIncomingEmail() waits 5 seconds, if this time is not sufficient, change the timeout.

// 10 seconds timeout
greenMail.waitForIncomingEmail(10_000, 1);

You've reached the end of this blog post. This was an overview of catch-all SMTP servers. If you know another useful server send me a message, and I will add it to the list.