SES:在 lambda 函数中访问电子邮件正文

Posted

技术标签:

【中文标题】SES:在 lambda 函数中访问电子邮件正文【英文标题】:SES: Accessing email body inside lambda function 【发布时间】:2017-03-25 03:42:18 【问题描述】:

我对 AWS 比较陌生,我正在尝试通过 Lambda 函数处理我的电子邮件。我已经在 node.js 中构建了这个:

'use strict';

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

    var http = require('http');
    var data = JSON.stringify(event);

    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 处理?

如果您需要查看发送的样本,请告诉我。

【问题讨论】:

【参考方案1】:

所以我所做的是将收到的电子邮件存储在 S3 存储桶中,而不是通知我的 api 新电子邮件已到达(发送文件名)。最后在我的 api 中从 S3 读取、解析、存储和删除。

SES 规则:

Lambda 通知功能:

请注意,第一条规则创建的 S3 文件的名称与消息 ID 相同,因此为 '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));
            context.succeed();
        );
    );

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

API 函数(PHP - Laravel):

请注意,我正在使用基于 Plancake 电子邮件解析器(链接 here)的电子邮件解析器,并进行了一些我自己的更改,如果需要,我将进行编辑以显示源代码。

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]);

【讨论】:

感谢您的跟进。遇到同样的问题。我们必须跳过这么多循环才能获得消息体,这似乎有点愚蠢(更不用说效率低下)。这仍然是您今天使用的相同解决方案吗? @DaveJ 不幸的是:/ @MatheusSimon - 我正在尝试使用 laravel 5.4 运行与您自己非常相似的东西,我使用 SES 接受所有传入的电子邮件,它们当前存储在 S3 中,我最初的想法是解析它们在 Lambda 上使用 javascript 函数,然后将它们发送到 laravel 以存储在 DB 中并显示在应用程序中,我可以通过获取 SES 存储的 S3 对象使用您使用的电子邮件解析器完全避免 lambda 吗?感谢任何帮助,谢谢。 @Birdy 没有。我的 lambda 正在做的只是告诉我的 api 已收到一封新电子邮件。请注意,电子邮件的正文没有发送到我的 api,只有它的 id。这就是我发现的解决方法,可以完全按照您的意愿进行操作,因为 aws 与 api 对话的唯一方法(使用 Web 服务)是通过 lambda 函数。 ps.:我已经详细说明了这个解决方案,如果你愿意,我可以帮助你:gmail dot com 上的 mattzsimon @Birdy 摘要:不能使用没有 lambda 的 web 服务,不能用 lambda 发送正文,所以:收到电子邮件,存储在 s3 中,用 lambda 通知 api,通过 api 从 s3 获取正文,解析和存储, (可选:从 s3 中删除的电子邮件)。如果这个问题有帮助,请给它并竖起大拇指。谢谢【参考方案2】:

我有一个与另一个非常相似的解决方案,但步骤更少。可以设置 lambda 触发器。因此,我创建了一个存储桶myemailbucket,并将邮件从 SES 发送到该存储桶。然后我将我的 lambda 函数的触发器更改为存储桶 myemailbucket 中 s3 中的任何创建事件。

【讨论】:

以上是关于SES:在 lambda 函数中访问电子邮件正文的主要内容,如果未能解决你的问题,请参考以下文章

AWS同账户不同地域资源访问(ses到lambda)

AWS SES 计划发送电子邮件(节点 SDK)

从自定义授权方 Lambda 函数访问 POST 请求正文

亚马逊 SES 反弹

HTTP 请求正文未通过 AWS API Gateway 访问 AWS lambda 函数

如何授权 lambda 使用 CDK 执行 ses:SendEmail?