Over the past four years at Foster Made, I have been afforded many opportunities to work on exciting and custom projects. Each has presented its own set of unique challenges, but all of them have had one thing in common. Emails. That’s right. I feel that email notifications are the one aspect of every project that is most often taken for granted.
Sending emails from websites and web applications has become much easier in recent years, especially with the advent of third party sending services such as Mandrill, SparkPost, SendGrid, and MailGun, among others. As developers, we can just add SMTP settings for these services to a configuration file, add some boilerplate code or make an API call, and we’re off and running. But how should you properly set up your application to prevent spamming of countless users when working with replicated production data in a development environment?
Our workflow for large scale email testing currently includes using MailCatcher or development environments. MailCatcher will simply intercept all outgoing emails so you can view them from an interface, without them being sent to the end user — this has saved me from a lot of worry and headaches when testing locally.
Setting up MailCatcher is relatively straightforward. At Foster Made we are a Mac and Linux shop. On those platforms, you can simply install MailCatcher at a command prompt by typing:
gem install mailcatcher
Here are a few environmental considerations to note before proceeding:
Ubuntu 16.04 (xenial xerus):
apt-get install build-essential libsqlite3-dev ruby1.9.1-dev
apt install build-essential libsqlite3-dev ruby-dev
Moving along, once you have worked through the installation and successfully installed MaiCatcher, you can run it manually by issuing this command:
mailcatcher --ip 0.0.0.0
At this point you should be able to send emails by default on port 1025. (Note: this can be changed to whatever port you prefer, but for the purposes of this post, we will stick with port 1080 to avoid confusion). You will also be able to access the simple web interface using your IP or test domain on port 1080 at http://localhost:1080/. Here is a screenshot of what that nterface looks like:
Over the past couple of years, two projects in particular have stood out where Mailcatcher has been a lifesaver. The first is a custom newsletter sending module that we wrote for ExpressionEngine that is currently sending up to 100,000 emails or more per week in production. The second is a scheduling application developed on the Django Framework, and by its very nature, communication is performed solely via email. To show you how easy it was to get things working for these two projects, I would like to share my configuration variables with you.
For ExpressionEngine, as a standard setup we use Focus Lab’s Master Config. This allows us to configure each environment’s email settings to be different. Based on this, we can define different SMTP settings for development, staging, and production environments. For development, it is easy for us to implement MailCatcher and keep those changes checked into Git by adding a development configuration file and adding the following SMTP settings to that config:
$env_config['smtp_port'] = '1025'; $env_config['mail_protocol'] = 'smtp'; $env_config['smtp_server'] = '127.0.0.1'; $env_config['smtp_username'] = ''; $env_config['smtp_password'] = '';
Similarly for Django, in my development configuration file, I usually set up email to send through MailCatcher using the following config variables. As a failsafe, if you define DEBUG as True in your production or staging config, you can also add these settings after an “if DEBUG:” conditional, as mentioned in the MailCatcher documentation.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = '127.0.0.1' EMAIL_PORT = 1025 EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = ''
I personally prefer to develop using Vagrant. As a last step, I make it a habit to add an upstart script or a systemd service to my Ubuntu Vagrant boxes to make sure MailCatcher is always up and running when I am testing.
I found it easier in the past to use an upstart script on Ubuntu 14.04. You can create an upstart script by adding the following code a file called /etc/init/mailcatcher.conf.
description "Mailcatcher" start on runlevel  stop on runlevel [!2345] respawn exec /usr/bin/env $(which mailcatcher) --foreground --http-ip=0.0.0.0
In Ubuntu 16.04, I have moved on to using systemd to manage services that need to run on boot. Here are a few steps to get this working.
[Unit] Description=Mailcatcher Service After=network.service vagrant.mount [Service] Type=simple ExecStart=/usr/local/bin/mailcatcher --foreground --ip 0.0.0.0 Restart=always [Install] WantedBy=multi-user.target
If you are developing on a Mac using MAMP, you can easily use MailCatcher there as well. To do that, follow the instructions above for installation on your Mac. Afterwards, you should be able to access MailCatcher on port 1080 using http://localhost:1080/. Next we will need to modify our MAMP php.ini file:
Find the [mail function] section of your php.ini file and modify it to send emails over port 1025. Also, uncomment the sendmail_from and sendmail_path to look similar to the code below, changing the email address and port to match what you need:
[mail function] ; For Win32 only. SMTP = localhost smtp_port = 1025 ; For Win32 only. sendmail_from = email@example.com ; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). sendmail_path = /usr/bin/env catchmail —smtp-port 1025 -f firstname.lastname@example.org
If you were able to follow along with the instructions above, you should now have MailCatcher installed. After verifying that it’s working properly and intercepting messages, you can rest easy knowing that you won’t accidentally spam users if you are testing with production data on your local development environment. However, this only scratches the surface. There are many other aspects of email testing to discuss: unit testing email on various frameworks, hijacking email requests for testing purposes, and other scenarios. I'll dive into some of those topics in future posts, so stay tuned for more.
Posted in #Technologies