Using Lumen and Mandrill to Process Incoming Mail

Published

One of my clients has a WordPress site which makes use of several different contact forms. Powered by the Contact Form 7 plugin, they send their data to an email address when the user submits the form. Recently my client decided that they wanted to send a "Thank you" email as a response to each person who filled out a particular form, and they asked me to set that up for them. I have been using Mandrill for some of my larger applications, and it seemed to me that by combining the Mandrill Incoming Mail API with Lumen, the micro-framework from Laravel, setting up a micro-service to solve this problem would be very easy. Here is how I did it:

First we should create a subdomain on the client's main site, specifically for this project: inbox.clientdomain.com. This is where we will set up our Lumen Application. Next we need to point the MX records for that subdomain to Mandrill. Within your Mandrill dashboard, go to the "Inbound" section and add your new domain to the domain list there. Once that is done Mandrill will provide you with the necessary MX details, and you can also verify that you MX records have been set appropriately. Next, click on the "routes" button for your new domain. When email is received at inbox.clientdomain.com we can assign the URL we want Mandrill to send it to. For this project I set up all mail received at "[email protected]" to be sent to the URL "http://inbox.clientdomain.com/contact". We could set up other email addresses on that domain to be sent to other places, but this is all we will need for now.

Here is the basic structure of what our micro-service will do:

  • Receive the POST data from Mandrill containing the email message content
  • Parse out the Sender's email address and Name (if it was provided);
  • Save a copy of the message to a MySQL database, for archival purposes
  • Send a "Thank you" email to the sender of the original message
  • Send a notification to the client that a new message has been received.

First we need to install Lumen:

$ composer create-project laravel/lumen inbox --prefer-dist

This will install Lumen to the "inbox" folder in my projects directory. We are going to be using Facades and Eloquent in this project, and personally I prefer using the phpdotenv config files, so we need to uncomment those lines in /bootstrap/app.php to enable those features:

1Dotenv::load(__DIR__.'/../');
2
3// ...
4
5$app->withFacades();
6
7$app->withEloquent();
bootstrap/app.php

Now we need to pull in two additional packages via composer: illuminate/mail and guzzlehttp/guzzle:

$ composer require illuminate/mail
$ composer require guzzlehttp/guzzle

Lumen does not come with email functionality out of the box, so we are pulling that in here. Also, we need Guzzle to make use of the Mandrill API. Add a config file called /config/services.php like so:

1return array(
2 'mandrill' => [
3 'secret' => env('MANDRILL_API_KEY'),
4 ],
5);
config/services.php

Find the .env.example file in your project's root folder and save it as ".env". This is where we will keep our environmental configuration values. Add a line for your Mandrill API key:

MANDRILL_API_KEY=XXXXXXXXXXXXXX

You should add 32 character random App key while you are here, as well as making sure that your DB credentails are accurate.

If you want to store copies of your messages in your database, you should create a migration and set up that database now:

1class CreateMessagesTable extends Migration
2{
3
4 /**
5 * Run the migrations.
6 *
7 * @return void
8 */
9 public function up()
10 {
11 Schema::create('messages', function (Blueprint $table) {
12 $table->increments('id');
13 $table->string('email');
14 $table->string('name');
15 $table->text('message');
16 $table->timestamps();
17 });
18 }
19
20 /**
21 * Reverse the migrations.
22 *
23 * @return void
24 */
25 public function down()
26 {
27 Schema::drop('messages');
28 }
29}

Now we are ready to set up our routing. In the file app/Http/routes.php, add these route:

1$app->post('/contact', [
2 'uses' => 'App\Http\Controllers\ContactController@newMessage'
3]);
4
5$app->get('/', function () use ($app) {
6 return response()->json(['ok'], 200);
7});
app/Http/routes.php

The first route received the Mandrill Post data and sends it to the newMessage method on our soon to be created ContactController. The second route is just a convenient way to ping the service and make sure it is running.

If this were a larger application, I would next suggest thinking about abstracting our code into a library for handling Mandrill Post Data, and then injecting that library into our ContactController. However, this is such a small service that I don't think we need to really worry about that. True, if we find that we want to handle multiple endpoints with this code, we will gain a lot by abstracting this code into a library that can be used wherever we need it (in keeping with the DRY spirit.) However, we are only concerned with one endpoint for now, so I am going to keep all of the logic within the newMessage controller method.

Create a file called App/Http/Controllers/ContactController.php with a method called newMessage:

1/**
2* Handle a Mandrill Inbound API message
3*
4* @param Request $request
5* @return Response
6*/
7public function newMessage(Request $request)
8{
9 // Gather the POST data from Mandrill - if nothing is there, we can call it quits
10 $mandrillEvents = $request->input('mandrill_events', null);
11 if (!$mandrillEvents) {
12 return response()->json(['ok'], 200);
13 }
14
15 // Decode the WebHook data and get the text content of the email
16 $mail = json_decode($mandrillEvents);
17 $body = $mail[0]->msg->text;
18
19 // Extract the sender's email and the story from the body of the email
20 $senderEmail = $this->parseSenderEmail($body);
21 $senderName = $this->parseSenderName($body);
22
23 //Write the story to the DB
24 DB::table('messages')->insert([
25 'email' => $senderEmail,
26 'name' => $senderName,
27 'message' => $body,
28 ]);
29
30 //Send a confirmation email to the submitter
31 $this->acknowledge($senderName, $senderEmail);
32
33 // Send a notification that a story was received
34 return $this->notify($body, $senderName);
35}
app/Http/Controllers/ContactController.php

First we gather the mandrill_events Post data from the http request. Note that Lumen does not provide an "Input::" facade, we need to get our input directly from the $request object. Per the Mandrill Inbound API, this is a json encoded array of data representing the email message. The $mail array represents the decoded message data.

This email is being sent to us via WordPress, so we have quite a bit of controll over the format it is being sent to us in. We don't need to concern ourselves with the headers, or the sender's email address (in this case the sender is our WordPress installation.) All we need is the text from the body of the message, which we gather like so: $body = $mail[0]->msg->text;. Now we have the text content of the message and we can do whatever we need to with it.

In my case, I have set up the WordPress contact form to include the Sender's name and email address within the body of the message. To extract them, I created two private methods (parseSenderEmail() and parseSenderName()). The implementation of these methods will depend greatly on the format of the message - your specific implementations will be different from mine so I have not shown them here.

Next we write a copy of the message to the database using the DB facade, and then we send two emails: The first is the acknowledgement message sent to the person who contacted us in the first place, and the second is the notification sent to the client. Here are those two methods:

1/**
2 * Send an acknowledgement email to the person who contacted us
3 *
4 * @param $senderName
5 * @param $senderEmail
6 */
7private function acknowledge($senderName, $senderEmail)
8{
9 if (filter_var($senderEmail, FILTER_VALIDATE_EMAIL)) {
10 Mail::send('emails.thankyou', [], function($message) use ($senderName, $senderEmail) {
11 $message->from('[email protected]', 'Client Name');
12 $message->subject('Thank you for contacting us');
13 $message->to($senderEmail, $senderName);
14 });
15 }
16}
17
18 /**
19 * Send a notification to '[email protected]`
20 * @param $body
21 * @return
22 */
23private function notify($body, $senderName)
24{
25 Mail::send('emails.notify', ['body' => nl2br($body)], function($message) use ($senderName) {
26 $message->from('[email protected]', 'Client Name');
27 $message->subject('A New Contact Request from ' . $senderName);
28 $message->to('[email protected]', 'Client Name');
29 });
30
31 return response()->json(['ok'], 200);
32}
app/Http/Controllers/ContactController.php

In the acknowledge() method we are first making sure that the email address we were provided with is valid. If it is, we send an email address to that address with some boilerplate text, which comes from the /resources/views/emails/thankyou.blade.php file.

In the second method, we are essentially forwarding the content of the message to the client directly. We pass the $body content to a blade file called /resources/views/emails/notify.blade.php, which looks like this:

1<!DOCTYPE html>
2<html lang="en-US">
3<head>
4 <meta charset="utf-8">
5</head>
6<body>
7{!! $body !!}
8</body>
9</html>

Note the use of nl2br() to keep the basic formatting in place. We read the text version of the message, which uses newline characters that will be lost when we show the text in html. Using nl2br() to convert the newlines to "<br />" tags maintains the basic message format that we are expecting.

That is really all there is to it. Deploy the code to the client's server and you should be good to go.

About the Author

Ryan C. Durham is a software developer who lives in southern Washington with his wife and daughter. His areas of interest include PHP, Laravel, Rust and PostgreSQL, as well as organizational efficiency and communications strategies.

You can find him on GitHub and LinkedIn.