Matheus Simon Matheus Simon - 25 days ago 6
PHP Question

HOWTO: Email Server Amazon?

I am trying to build an email server for my domains... What I'm doing now is receiving emails through SES and storing them in a S3 bucket, then when a user access the inbox it fetches the new emails and store them in my EC2 instance database.

Though it works I'm not completely satisfied with this solution, does anyone know any other/better ways to work this receiving-storing-accessing problem?

Thanks in advance.

Answer

I've resolved this and posted the answer on another question I'd made here.

But I'll repost it here anyway:

So what I did was storing the email received in an S3 bucket, than notifying my api that a new email has arrived (sending the file name). Finally read from S3, parsed, stored and deleted from S3, inside my api.

SES rules: SES rules

Lambda notifying function:

Note that the name of the S3 file created by the first rule is the same as the messages id, hence 'fileName': event.Records[0].ses.mail.messageId.

'use strict';

exports.handler = (event, context, callback) => {

    var http = require('http');
    var data = JSON.stringify({
        'fileName': event.Records[0].ses.mail.messageId,
    });

    var options = {
        host: 'my.host',
        port: '80',
        path: '/my/path',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json; charset=utf-8',
            'Content-Length': data.length
        }
    };

    var req = http.request(options, function(res) {
        var msg = '';

        res.setEncoding('utf8');
        res.on('data', function(chunk) {
            msg += chunk;
        });
        res.on('end', function() {
            console.log(JSON.parse(msg));
        });
    });

    req.write(data);
    req.end();
};

Api function (PHP - Laravel):

Note that I'm using an email parser that's based on Plancake Email Parser (link here) with some changes of my own and if needed I'll edit to show the source.

public function process_incoming_email(Request $request)
{
    $current_time = Carbon::now()->setTimezone('Brazil/East'); // ALL TIMEZONES: http://us.php.net/manual/en/timezones.others.php

    try
    {
        if ($request->has('fileName')
        {
            $file_name = $request->input('fileName');

            // GET CREDENTIALS AND AUTHENTICATE
            $credentials = CredentialProvider::env();
            $s3 = new S3Client([
                'version' => 'latest',
                'region'  => 'my-region',
                'credentials' => $credentials
            ]);

            // FECTH S3 OBJECT
            $object = $s3->GetObject(['Bucket' => 'my-bucket', 'Key' => $file_name]);
            $body = $object['Body']->getContents();

            // PARSE S3 OBJECT
            $parser = new EmailParser($body);
            $receivers = ['to' => $parser->getTo(), 'cc' => $parser->getCc()];
            $from = $parser->getFrom();
            $body_plain = $parser->getPlainBody();
            $body_html = $parser->getHTMLBody();
            $subject = $parser->getSubject();

            $error_message;

            // PROCESS EACH RECEIVER
            foreach ($receivers as $type => $type_receivers)
            {
                foreach ($type_receivers as $receiver)
                {
                    // PROCESS DOMAIN-MATCHING RECEIVERS
                    if(preg_match("/@(.*)/", $receiver['email'], $matches) && $matches[1] == self::HOST)
                    {
                        // INSERT NEW EMAIL
                        $inserted = DB::table('my-emails')->insert([
                            // ...
                        ]);
                    }
                }
            }

            // ADD ERROR LOG IF PARSER COULD NOT FIND EMAILS
            if($email_count == 0)
            {
                DB::table('my-logs')->insert(
                    ['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Could not parse received email or find a suitable user receiving email.') . ' File: ' . $file_name]
                );
            }
            // DELETE OBJECT FROM S3 IF INSERTED
            else if(count($emails) == $email_count)
            {
                $s3->deleteObject(['Bucket' => 'my-bucket', 'Key' => $file_name]);

                // RETURN SUCCESSFUL JSON RESPONSE
                return Response::json(['success' => true, 'receivedAt' => $current_time, 'message' => 'Email successfully received and processed.']);
            }
            // ADD ERROR LOG IF NOT INSERTED
            else
            {
                DB::table('my-logs')->insert(
                    ['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Inserted ' . count($emails) . ' out of ' . $email_count . ' parsed records.') . ' File: ' . $file_name]
                );
            }
        }
        else
        {
            // ERROR: NO fileName FIELD IN RESPONSE
            DB::table('my-logs')->insert(
                ['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'Incorrect request input format.') . ' Input: ' . json_encode($request->all())]
            );
        }
    }
    // ERROR TREATMENT
    catch(Exception $ex)
    {
        DB::table('my-logs')->insert(
            ['sender' => $request->ip(), 'type' => 'error', 'content' => ($error_message = 'An exception occurred while processing an incoming email.') . ' Details: ' . $ex->getMessage()]
        );
    }

    // RETURN FAILURE JSON RESPONSE
    return Response::json(['success' => false, 'receivedAt' => $current_time, 'message' => $error_message]);
}