Sending Mock Emails with Docker

Introduction

I've been working on a new version of the plugin that allows people to subscribe to UConn Today. One of the goals was to see how the plugin handles sending emails to a listserv which has the subscriber list. I wanted something that was going to be easy to incorporate into the project which uses Docker.

The goal is to be able to send an email from the web server and then see it arrive at the mailhog SMTP server. After a lot of research (and not having any idea what I was doing), I found these two articles:

The architecture for testing the emails is to use two containers

  1. A web server with PHP that is configured to send emails using mhsendmail ("A sendmail replacement which forwards mail to an SMTP server."). This will be an extension of the php 7.2 docker image we use in all of our projects.
  2. An SMTP server image that uses mailhog which is "is an email testing tool for developers".

Updating the web server

Because we don't necessarily need email in every project, I decided create a new image based on our uconn/php72-official image. Based on the articles above, the other steps were pretty straightforward. But I got a little hung up on how Docker networking works so I'll describe the kinds of errors I got and how I fixed them. But first, here's the Dockerfile I used.

        
          FROM uconn/php72-official

COPY mhsendmail/php.ini /usr/local/etc/php/php.ini

RUN apt-get update 
  && apt-get install --no-install-recommends -y ca-certificates curl git nano 
  && rm -rf /var/lib/apt/lists/* 
  && curl -Lsf 'https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz' | tar -C '/usr/local' -xvzf -

ENV PATH /usr/local/go/bin:$PATH
RUN go get github.com/mailhog/mhsendmail 
  && cp /root/go/bin/mhsendmail /usr/local/bin/mhsendmail        
    

Unlike in the articles I decided to keep the php.ini file in the repo for reference.

Docker Networking

Once I'd made a new image called mailhog-test, it was time to update my docker-compose file. So again, following the tutorials, I updated it like this.

        
          version: "3"
services:
  web:
    image: mailhog-test:latest
    environment:
    # wordpress env variables
    ports:
      - "80:80"
    volumes:
    # a bunch of volumes for wordpress
    links:
      - db:mysql
    privileged: true
  # the mailhog server
  mail:
    image: mailhog/mailhog:latest
    ports:
      - "1025:1025"
      - "8025:8025"
# other services below...        
    

Next, I went into the web server and tried to send an email with php.

        
          $ php -a
> mail("to.receiver@test.com", "Subject line", "message");        
    

But then I got....

Errors

The two most common errors were dial tcp 127.0.0.1:1025: getsockopt: connection refused and dial tcp: lookup mailhog on 127.0.0.11:53: no such host depending on how I tried to configure my hosts file. I could see that php was trying to send the email because of the port 1025 on localhost. But it wasn't getting there. But then it hit me!

Solution

When containers need to communicate with each other inside their network, they need to be referenced by the names of their services. In this case web and mail respectively. I was faithfully following the examples and had included this line in my php.ini file - sendmail_path = /usr/bin/mhsendmail --smtp-addr mailhog:1025. Note that it says mailhog:1025 as the address to go to. When I changed that to mail:1025 (the name of the service) it worked perfectly.

Conclusion

When working with Docker it always helps to try and think about what's going on with the application from Docker's perspective.

  • What's going on inside those containers?
  • What information is needed by the network?
  • Where are files and data trying to go?

Even though I'd networked other containers to each other, because I had no prior experience with email, I forgot to think about how the network worked. Fortunately, we now have an easy way to test email and see them come up in mailhog.

Posted by Adam Berkowitz