18
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

メールサーバーを捨てて、問い合わせ受付をサーバーレスで実現する その2(HTMLメールと添付ファイルに対応)

Posted at

目的

前回の記事メールサーバーを捨てて、問い合わせ受付をサーバーレスで実現するでメールを送信できるようになったが、添付ファイルがつけれなかったため添付ファイルに対応したかった(ついでにHTMLメールにも対応させてみました)

前回からの変更点

  • メールの設定情報(送り元、送り先、メール用バケット)をDynamoDBへ外出し(毎回変更するのが面倒だったため)
  • 添付ファイルとHTMLのメールに対応
  • IAMロールのポリシーを修正(コードの変更に伴って)

image

DynamoDBの設定

us-east-1リージョンに「mail_settings」というテーブルを作成し、以下itemを追加します。
※プライマリパーティションキーはaccountId

{
  "accountId": "<AWS Account No>",
  "bucketName": "inquiry-bucket",
  "sourceAddress": "no-reply@xxxx.co.jp",
  "toAddress": [
    "info@xxxx.co.jp"
  ]
}

IAMロールの設定

ポリシーを以下のように変更しました。
DynamoDBへのアクセス権付与とSESの権限を「SendEmail」から「SendRawEmail」に変更。

inquiryPolicy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1474188898000",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "Stmt1474188915000",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:ReplicateObject"
            ],
            "Resource": [
                "arn:aws:s3:::inquiry-bucket"
            ]
        },
        {
            "Sid": "Stmt1474294979000",
            "Effect": "Allow",
            "Action": [
                "ses:SendRawEmail"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "Stmt1476151970000",
            "Effect": "Allow",
            "Action": [
                "dynamodb:GetItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:us-east-1:<accountId>:table/mail_settings"
            ]
        }
    ]
}

コードの修正

以下のように修正しました。
fs、child_process、mailparser、mailcomposerモジュールと一緒にzip化しアップロードしてください。

inquiryFunction
'use strict';

console.log('Loading function');
const AWS = require('aws-sdk');
const fs = require('fs');
const s3 = new AWS.S3();

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

    const accountId = context.invokedFunctionArn.match(/\d{3,}/)[0];
    const messageId = event.Records[0].ses.mail.messageId;
    const filepath  = '/tmp/message';

	const generator  = (function *() {

        try {
            // DynamoDBからメールの設定情報を取得
            const mailSettings  = yield getmailSettings(accountId, generator);
            const bucketName    = mailSettings.bucketName;
            const addressInfo   = {
                toAddress       : mailSettings.toAddress,
                sourceAddress   : mailSettings.sourceAddress
            };

            // S3からメッセージを取得する
            const s3Object = yield getObject(messageId, bucketName, generator);

            // Getしたオブジェクトを書き込み
            fs.writeFileSync(filepath, String(s3Object.Body));

            // エンコーディング
            yield encodingMessage(filepath, generator);

            // 送信内容定義
            const sendMailInfo = yield setSendMailInfo(filepath, generator);

            // 送信内容作成
            const message = yield createMessage(sendMailInfo, addressInfo, generator);

            // メッセージ送信
            yield sendRawEmail(message, generator);

            // S3オブジェクトキー書き換え
            yield copyObject(messageId, bucketName, sendMailInfo.from[0].address, generator);
            yield deleteObject(messageId, bucketName, generator);

			callback(null,'succeed!');

		} catch (e) {
		    callback(e.message);
		}
	})();

	/* 処理開始 */
	generator.next();
};

// DynamoDBからメールの設定情報を取得
function getmailSettings(accountId, generator) {

    const docClient = new AWS.DynamoDB.DocumentClient();
    const table = 'mail_settings';

    const params = {
        TableName: table,
        Key: {
            'accountId': accountId
        }
    };

    docClient.get(params, function(err, data) {
        if(err) {
            console.log(err, err.stack);
			generator.throw(new Error('getmailSettings Error'));
			return;
        }
        console.log('got mailSettings');
        generator.next(data.Item);
    });
}

// S3からメッセージを取得する
function getObject(messageId, bucketName, generator) {

    const params = {
        Bucket: bucketName,
        Key: messageId
    };

    console.log('Getting object from ' + bucketName);

    s3.getObject(params, function(err, data) {
        if(err) {
            console.log(err, err.stack);
			generator.throw(new Error('getObject Error'));
			return;
        }
        console.log('Got object');
        generator.next(data);
    });
}

// エンコーディング
function encodingMessage(filepath, generator) {
    const exec = require('child_process').exec;
    const cmd  = 'iconv -f iso-2022-jp -t utf-8 ' + filepath;

    console.log('encoding command is ' + cmd);

    const child = exec(cmd, function(err, stdout, stderr) {
        if(err) {
            console.log(err, err.stack);
			generator.throw(new Error('encoding Error'));
			return;
        }
        console.log(stdout);
        // 再度書き込み
        fs.writeFileSync(filepath, stdout);
        generator.next();
    });
}

// 送信内容定義
function setSendMailInfo(filepath, generator) {

    const MailParser = require('mailparser').MailParser;
    const mailparser = new MailParser();

    mailparser.on("end", function(mail_object){
        const from = mail_object.from;
        const subject = mail_object.subject;
        const attachments = mail_object.attachments;
        const html = mail_object.html;
        const text = mail_object.text;

        if(attachments) {
           // 添付ファイルがある場合、filenameを設定
           for(let i = 0, max = attachments.length; i < max; i++) {
               attachments[i].filename = attachments[i].fileName;
           }
        }

       const sendMailInfo = {
           from        : from,
           subject     : subject,
           text        : text,
           html        : html,
           attachments : attachments
       };
       generator.next(sendMailInfo);
    });

    fs.readFile(filepath, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            generator.throw(new Error('setSendMailInfo Error'));
            return;
        }
        mailparser.write(data);
        mailparser.end();
    });
}

// 送信内容作成
function createMessage(sendMailInfo, addressInfo, generator) {
    const mailcomposer = require('mailcomposer');

    const mailOption = {
        from         : addressInfo.sourceAddress,
        to           : addressInfo.toAddress,
        replyTo      : sendMailInfo.from[0].address,
        subject      : sendMailInfo.subject,
        html         : sendMailInfo.html,
        text         : sendMailInfo.text,
        textEncoding : 'quoted-printable',
        attachments  : sendMailInfo.attachments
    };

    const mail = mailcomposer(mailOption);

    mail.build(function(err, msg) {
        if (err) {
            console.log(err, err.stack);
            generator.throw(new Error('createMessage Error'));
            return;
        }
        console.log(msg);
        generator.next(msg);
    });

}

// メッセージを送信する
function sendRawEmail(message, generator) {
    const ses = new AWS.SES({'region' : 'us-east-1'});

    // SES送信用パラメータ
    const params = {
        RawMessage: {
            Data: message
        }
    };

    console.log('Sending Email ..');
    ses.sendRawEmail(params, function(err, data) {
        if (err) {
            console.log(err, err.stack);
            generator.throw(new Error('SES Error'));
            return;
        }
        console.log('Send Successful');
        console.log(data);
        generator.next();
    });
}

// S3オブジェクトコピー
function copyObject(key, bucketName, sourceAddress, generator) {

    const now = new Date();

    const params = {
        CopySource: bucketName + '/' + key,
        Bucket: bucketName,
        Key: now + ' from ' + sourceAddress
    };

    console.log('Copying Object from ' + params.CopySource + ' to ' + params.Bucket + '/' + params.Key);

    s3.copyObject(params, function(err, data) {
        if(err) {
            console.log(err, err.stack);
            generator.throw(new Error('CopyObject Error'));
            return;
        }
        console.log('Copied Successful');
        console.log(data);
        generator.next();
    });
}

// S3オブジェクト削除
function deleteObject(key, bucketName, generator) {

    const params = {
        Bucket: bucketName,
        Key: key
    };

    console.log('Deleting Object ' + params.Bucket + '/' + params.Key);

    s3.deleteObject(params, function(err, data) {
        if(err) {
            console.log(err, err.stack);
            generator.throw(new Error('DeleteObject Error'));
            return;
        }
        console.log('Deleted Successful');
        console.log(data);
        generator.next();
    });
}

以上

2週間くらい前に修正し、問題なく動いています。
ただ、やはり文字コードらへんの扱いに自信がないためなにか改善点があれば教えてください!

18
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?