Piping Emails to a Laravel Application

Introduction

In project management or support management tools, you will see this a lot: you can reply to an email message and it is automatically visible in a web application. Somehow, these tools were able to get those email messages right into their system.

In this article, we are going to have a look at how we can pipe emails to our Laravel 4 application. For that, we start with a fresh Laravel 4 project, installed through Composer as seen here.

composer create-project laravel/laravel your-project-name --prefer-dist

Creating an Artisan command

To be able to get our email into our application, we somehow have to pipe the email through the command line into our application. Luckily, Laravel has a command line tool called Artisan, which is able to perform multiple tasks. To see a list of all tasks that Artisan can run, you can run php artisan list in the root of your project.

In this case, we want it to perform a very specific task: accept a raw email and use it within our application. Unfortunately, this is not one of the basic functions Artisan can handle. We can easily extend it with a new command: php artisan email:parse. We’ll then just start up Artisan and perform a certain task, which, in this case, is called email:parse.

Our first step is to create this command. You can create new commands through Artisan’s own command for creating new commands. Just run the following in the root of your project:

php artisan command:make EmailParserCommand

If everything went smoothly, you will now find a file called EmailParserCommand.php in the directory app/commands. Open it up in your favorite editor and have a look at the $name and $description properties. We can customize this to whatever we want. By giving it a clear name and description, the command will be listed nicely in the list of artisan commands.

I changed it to this, for example:

/**
 * The console command name.
 *
 * @var string
 */
protected $name = 'email:parse';

/**
 * The console command description.
 *
 * @var string
 */
protected $description = 'Parses an incoming email.';

Register the command

When we run php artisan email:parse in the root of our project, you will receive a message that this command is not yet registered. Our next step is to make sure this command is registered within Artisan. Let’s open up the file app/start/artisan.php and add Artisan::add(new EmailParserCommand); to the end of the file to register our newly created command. We can now actually run the list command again to see our email:parse command listed. Notice that your just filled in name and description is displayed here.

Retrieve the raw email

Whenever the command is called through Artisan, it will always call the fire method. So initially, we have to add our parsing of the email in here. The email is currently sitting in our IO stream which we can retrieve from php://stdin. We open up this IO stream and then collect the email in small parts, until we have read the whole stream.

/**
 * Execute the console command.
 *
 * @return void
 */
public function fire()
{
    // read from stdin
    $fd = fopen("php://stdin", "r");
    $rawEmail = "";
    while (!feof($fd)) {
        $rawEmail .= fread($fd, 1024);
    }
    fclose($fd);
}

The email that was sent to our Artisan command is now residing in the $rawEmail variable. It is the whole email, containing headers, body and any attachments.

Parse the email

We have our raw email now, but I would prefer to have the email in multiple parts. I would like to retrieve the headers like subject and the body of the message. We could write our own code which splits all these parts, but someone already created a package which we can use within our application. This package is able to split our entire email into logical parts. Add the following line to your composer.json file and run composer update

"messaged/php-mime-mail-parser": "dev-master"

Now we need to make sure that we can actually use this package within our command, so we open up our app/command/EmailParserCommand.php again and we add these lines to the top:

use MimeMailParser\Parser;

Now we can parse our raw email into separate parts. Add the following lines of code to the end of the fire method.

$parser = new Parser();
$parser->setText($rawEmail);

$to = $parser->getHeader('to');
$from = $parser->getHeader('from');
$subject = $parser->getHeader('subject');
$text = $parser->getMessageBody('text');

We first create a new parser. Next, we set the raw email as the text of our parser and finally, we call all kinds of different methods to get data from the headers or the body.

You could now easily store your email within a database. For example, if you have an email entity, you could save the email like this to your database:

$email = new Email;
$email->to = $parser->getHeader('to');
$email->from = $parser->getHeader('from');
$email->subject = $parser->getHeader('subject');
$email->text = $parser->getMessageBody('text');
$email->save();

Working with attachments

You might even want to store any attachments which are attached to the email on your server. The email parser class can handle any attachments available. First, add the following lines again to the top of the app/command/EmailParserCommand.php class.

use Illuminate\Filesystem\Filesystem;
use MimeMailParser\Attachment;

Now we have to extend our fire method with some code which will handle attachments:

$attachments = $parser->getAttachments();

$filesystem = new Filesystem;
foreach ($attachments as $attachment) {
	$filesystem->put(public_path() . '/uploads/' . $attachment->getFilename(), $attachment->getContent());
}

Let’s have a look at what this part actually does. The first line retrieves the attachments from the email. The $attachments variable is an array of attachment objects. Next, we make sure a new FileSystem object is created, which will handle the saving of the files on our server. Then we start to iterate over all attachments. We call the put method of the FileSystem object, which accepts a path and the content of the file. In this case, we want to add the file to the public/uploads directory and use the file name the attachment actually has. The second parameter is the content of the actual file.

That’s it! Your files are now stored in public/uploads. Just make sure that your mail server can actually add files to this directory by setting the correct permissions.

Configuring our mail server

Until now, we prepared our whole application to retrieve, split and save our email. The code is quite useless, however, if you don’t know how to actually send an email to your newly created Artisan command.

Below you’ll find different methods to pipe your emails to your application, depending on which tools or mail servers you use. As an example, I want to forward support@peternijssen.nl to my application, which is located in /var/www/supportcenter.
Note that in the actual command you will see below, I have added --env=local each time, to make sure Artisan is being run as if we were on a development machine. If you are in production, you can of course remove this part.

CPanel

If you are using CPanel, you can click in the general menu on forwarders. Add a new forwarder and define which address you would like to forward to your application. Click on advanced settings and choose the pipe to program options.
In the input field, you can insert the following line:

/usr/bin/php -q /var/www/supportcenter/artisan --env=local email:parse

Note that CPanel uses the path relative to your home directory.

Exim

If on Exim, open up the file /etc/valiases/peternijssen.nl.
Make sure the following line is present within this file:

support@peternijssen.nl: “| /usr/bin/php -q /var/www/supportcenter/artisan --env=local email:parse

Run newaliases to rebuild the aliases database.

Postfix

On Postfix, make sure that the following lines are present and are uncommented in your /etc/postfix/main.cf file before continuing:

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases  

If you had to change the file, reload postfix by running service postfix reload

We can now create a new alias, which will be piped to our application.
Open up /etc/aliases and add the following line:

support@peternijssen.nl: "| /usr/bin/php -q /var/www/supportcenter/artisan --env=local email:parse"

Run newaliases to rebuild the aliases database.

Sendmail

With Sendmail you should first create an alias in the /etc/aliases file:

support: "| /usr/bin/php -q /var/www/supportcenter/artisan --env=local email:parse"

Run newaliases to rebuild the aliases database. Next, make sure the Artisan file is chmod to 755, so it’s executable.

Lastly, symlink the artisan file and php itself to /etc/smrsh

ln -s /usr/bin/php /etc/smrsh/php
ln -s /var/www/supportcenter/artisan /etc/smrsh/pipe.php

QMail

Depending on your installation, you have to make sure the following file is present:

/usr/local/qmail/mailnames/peternijssen.nl/support/.qmail

or:

/usr/local/qmail/mailnames/peternijssen.nl/.qmail-support

Open up either file and add the following line as content:

support@peternijssen.nl: "| /usr/bin/php -q /var/www/supportcenter/artisan --env=local email:parse"

Conclusion

Any framework that has a command line tool available will be able to process your emails. The code provided here is just a basic setup. Depending on your project, you might want to allow only certain email addresses to send an email to your application. Make sure you already filter emails in tools like postfix before piping to your application.

If you would like to work with some sort of ticket system, you could easily try to extract a support ticket id from the email subject and based on that, do multiple different actions with the email.

Keep a close look on the log file of your mail server. It will give you some hints when the actual piping fails on how to solve it.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://dextructables.com/ Judas Borbón

    I knew how to do this using an external SMTP like Mandrill and thought doing it using the local mail server would be much more difficult. Thanks!

  • willdurand

    Nice write up!

    I guess next step could be, for instance, to parse the body of the email, and the EmailReplyParser library might help you: https://github.com/willdurand/EmailReplyParser ;-)

    • Peter Nijssen

      Thanks! That’s definitely interesting. :)

  • Dave Ganley

    Thanks for the useful article. I’ve done this before with postmark app but it has a attachment size limit – I’ll definitely give this a try. Thanks again

  • Antonio Madonna

    Hi, great article! How would you manage to pipe the email to a different server ? Imagine you have separated mail and web server…

  • Xander

    good read! simple and effecive will use it pretty soon!