Mailr is a simple Python app written with Flask. It wraps Mailgun and Mandrill, both email services that provide a RESTful API for sending/receiving emails. Flask is enough for the purposes of the project since it provides good routing other nifty features without adding much bloat to the application.
Mailr accepts POST requests with a JSON object at an /email endpoint. The request should have the parameters below:
- to: the email address to send to.
- to_name: the name to accompany the email.
- from: the email address to send from.
- from_name: the name to accompany the email.
- subject: the subject line.
- body: the HTML body of the email.
An example request payload:
{
"to": "batman@waynemansion.com",
"to_name": "Batman",
“from”: “alfred@waynemansion.com”,
"from_name": "Alfred",
"subject": "Robin emergency.",
"body": "<h1>Master Wayne,</h1><p>The Joker broke Robin's nose, again.</p>"
}
An example successful response is:
{
"status": "success",
"message": "Email queued to be sent by Mailgun."
}
Additionally, the app has an /emails endpoint that accepts a page parameter (as in /emails/2 for instance). This endpoint requires Basic Auth with the username always being api and a password of your choosing. See the Environment Variables section for more on that.
##Installation
-
First, clone the repo:
git clone git@github.com:wa3l/mailr.git -
If you use virtualenv, which it's recommended that you do, then go ahead and create a new environment
virtualenv venvand activate itsource venv/bin/activate. This will create avenvdirectory which contains a local environment with all the libraries that you need, as well aspipand a Python interpreter. Thevenvdirectory is included in.gitignore. -
Now do
pip install -r requirements.txtto install all the required libraries listed inrequirements.txt. -
You need PostgreSQL installed on your system. Refer to the official website for instructions on how to install it. In my experience, Postgres.app is the most convenient way to install it on OS X. See the Environment Variables section for instructions on how to link a database to the code.
##Running
To run the app, you have two options.
-
By
python mailr.py. Note that you need to set your API keys in.bashrc. See the Environment Variables section for more information. -
By using foreman, a tool that lets you run multiple processes needed for your application using a
Procfile. This is necessary for deploying the app on Heroku. In fact, foreman is installed by default when you install the Heroku CLI tool, so this is a great way to have it installed. Once you have foreman installed, you need to set up your API keys in a.envfile. See the Environment Variables section for more information.Now you can do
foreman startTo start the app athttp://localhost:5000.
Once you get the app running, you can start sending POST requests to http://localhost:5000/email. You will need to quite foreman (ctrl-C) and do foreman start every time you make modifications to the code. Alternatively, you can get the best of both worlds by doing foreman run python mailr.py, which allows foreman to reload the code every time you make a change.
API keys are not kept in the git repository for obvious reasons. To set your own API keys, simply create a .env file in the main directory with the following content:
MAILGUN_KEY = your_mailgun_api_key
MAILGUN_URL = your_mailgun_api_url
MANDRILL_KEY = your_mandrill_api_key
MANDRILL_URL = your_mandrill_api_url
DATABASE_URL = link_to_your_database
MAILR_KEY = api_key_used_for_/emails
MAILR_ENV = Development (set to "Production" on production server)
foreman will make sure to load those environment variables when you startup the app without having to spam your .bashrc. This prevents the app from spamming your .bashrc and makes it easy to set those variables on multiple environments without conflicts. For instance, you can use the following command to set your Mailgun API key on Heroku:
heroku config:set MAILGUN_KEY=mailgun_api_key
However, if you choose not to use foreman, then you can add your Mailgun API key, for instance, by exporting it in your ~/.bashrc(or whatever file you use):
export MAILGUN_KEY=your_mailgun_api_key
Whichever way you end up using, you need to set all the variables mentioned above for the app to work correctly.
The required fields are: to, to_name, from, from_name, subject and body.
The service field is optional and only accepts the value mailgun or mandrill. It is otherwise ignored.
Similarly, the deliverytime field is also optional and accepts valid Unix timestamps between 0 and three days in the future. The three-day rule is a requirement by Mailgun, so I opted to make it a requirement for both for simplicity. Additionally, Mandrill accepts a UTC timestamp in the YYYY-MM-DD HH:MM:SS format, but I chose to accept only Epoch timestamps to make the API uniform. Another solution would be to accept multiple date formats and convert them accordingly depending on the service used. This, however, makes validation more painful to manage. Please note that Mandrill does not offer free delayed delivery emails, so you need to have a positive balance for this to work on Mandrill.
The to and from fields have to be valid email addresses. This is determined by scanning the given string against the following regex "[\w\.\-]*@[\w\.\-\+]*\.\w+". Admittedly, this can be improved, but I chose to keep it simple for the purposes of this app. Regular expressions made for email addresses can get pretty nasty. There are, however, limits on the minimum and maximum lengths of email addresses. I use 3 and 254, respectively.
The subject line and body fields are required, and thus have a minimum length of 1 character. The subject line's maximum length is 78. The body field has an upper limit of 25MB.
If any of the above rules are not met in the provided JSON data, the app will respond with a 400 BAD REQUEST and something like:
{
"status": "error",
"message": "Invalid email address."
}
The app send an email with the provided HTML body. Additionally, it processes the provided HTML to produce a plaintext version and sends that along with the HTML. This is done via Aaron Swartz's html2text which happens to produces valid Markdown too!
Mailgun is used as the default email service. Mandrill is used as backup. The decision is based on Mailgun's faster delivery, clearer documentation, and better error responses. Mandrill promises appropriate error codes/messages but have in my experience only responded with 500 Internal Error headers for any kind of error.
However, this behavior can be changed by modifying the SERVICES variables in config.py. Additionally, you can specify an email service in the JSON request using the service field. Setting this field takes precedence over the default settings.
Finally, regardless of the method you use to specify the preferred email service, the app will automatically determine if your preferred service is having issues/timeouts and will try the secondary service to send the email.
You need to access /emails/page_number to access the emails that pass through the app. This endpoint requires Basic Auth with the username being api and the password set in .env. It returns a JSON object that looks like this:
{
"page": 1,
"emails": {
"1": "{'to': 'batman@waynemansion.com', 'to_name': 'Batman', 'from': 'alfred@waynemansion.com', 'from_name': 'Alfred', 'subject': 'Robin!', 'text': 'Robin is missing!', 'html': '<p>Robin is missing!</p>', 'service': 'Mailgun', 'deliverytime': '1408424058'}",
"2": "{'to': 'alfred@waynemansion.com', 'to_name': 'Alfred', 'from': 'batman@waynemansion.com', 'from_name': 'Batman', 'subject': 're:Robin!', 'text': 'Good.', 'html': '<p>Good.</p>', 'service': 'Mailgun', 'deliverytime': '1408424058'}"
}
}
To run the test suite do foreman run python -m unittest discover. Using foreman run will load the environment variables into the test environment. If you don't use foreman, then adding the variables in config.py to your .bashrc will do the trick. The test environment uses an in-memory SQLite database for speed and simplicity. All test modules are kept under the tests/ directory.
A demo app is live here. Please contact me for the API key for the /emails endpoint used for viewing saved emails.
- Don't forget to set your content-type to
application/jsonin the request headers. Theemailendpoint will only respond to JSON POST requests. - I used
urllib2for making the POST requests originally and have working code that be seen in the commit history. However, I opted to use Requests because it simplifies the logic and makes code more readable. - More test coverage is coming.
Wael Al-Sallami | about.me/wael.