メールに添付されたCSVファイルの取り込み→加工→転送をAWSのサーバレスで構築する方法の考察 part3 添付ファイル送信編

# やりたいこと（part1,2と同様）
下記工程を人の手でやっていたところを自動化したいそう。
なんとなくAWSだけで構築できそうなので考えてみた
1. データが記入されたCSVファイルをメールで受け取る
1. 受けとったCSVファイルを確認する
1. 記入内容にミスが無ければサーバに格納
1. 記入内容にミスがあれば、記入列を色付けして返信
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/171c6913-74eb-e300-c5f5-4fec69e93cc7.png)

↑イメージこんな感じ。
を
↓こんな感じにしてみようかと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/b56b9815-c46b-f3b0-a7e6-4dac73253f7c.png)
1. SESでSCVファイルが添付されたメールを受信する
1. 受信したメールをS3に格納
1. S3格納をトリガーにLambda発火
1. LambdaでCSVファイルの加工を行う。（記入ミスがある場合SESを使用して返信）
1. 加工したCSVファイルをS3に格納
1. 時間発火（EventBridge+Lambda）でCSVファイルをserverに送信（FTPかな？）

って感じです。
とりあえずやってみよう。笑笑

# とりあえずLambdaでメールを送信できるようにする
この部分！！
一旦メールを送れるようにしてから、ファイルを添付する方法を考えようと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/ae051927-ff6d-d345-0e58-0f70a798045b.png)


とりあえずロール追加。
あまりFullAccessは良くないと分かっていても検証ではやってしまいますね、
![27.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/2c4a620a-ae2d-59aa-cf86-0227f072bb05.png)

Lambda+SESでメールを送信するためには、送信先のメールアドレスの認証が必要になるそうです。
「SES」→「検証済みID」→「IDの作成」からメアドの認証をします。
IDタイプ：Eメールアドレス
Eメールアドレス：送信したいメールアドレス
![29.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/b0dd015c-d427-d3c7-4bdf-f1bbe193c2f3.png)

認証されたらAmazonからこんなメールが届きます。笑笑
![30.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/025850a0-0e05-e080-4bc6-3d44fcf34464.png)

part2で作成したLambdaにメールを送信するコードを追記します。
メール受信したら返信するだけのコードです。
client.send.emailでメールの件名とか文を作成してますね。
~~~python:sendmail.py
import boto3
import base64
import email
import urllib.parse
import json
from logging import getLogger, INFO

logger = getLogger(__name__)
logger.setLevel(INFO)

s3 = boto3.resource('s3')

#追加-------------------------------------------------------------------------------------------------
#メアド
SRC_MAIL = "送信元メールアドレス"
DST_MAIL = "送信先メールアドレス"
REGION = "SESのリージョン"

#メール送信
def send_email(source, to, subject):
    client = boto3.client('ses', region_name=REGION)

    response = client.send_email(
        Source=source,
        Destination={
            'ToAddresses': [
                to,
            ]
        },
        Message={
            'Subject': {
                'Data': subject,
            },
            'Body': {
                'Text': {
                    'Data': "返事やで～",
                },
            }
        }
    )
    
    return response
#ここまで-------------------------------------------------------------------------------------------------

def lambda_handler(event, context):
    print("--------- logger.info の出力 ---------")
    logger.info(json.dumps(event))
    
    # S3のデータ取得
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    messageid = key.split("/")[1]
    
    try:
        response = s3.meta.client.get_object(Bucket=bucket, Key=key)
        email_body = response['Body'].read().decode('utf-8')
        email_object = email.message_from_string(email_body)

        #メールから添付ファイルを抜き出す
        for part in email_object.walk():
            print("maintype : " + part.get_content_maintype())
            if part.get_content_maintype() == 'multipart':
                continue
            # ファイル名の取得
            attach_fname = part.get_filename()
            print(attach_fname)

            # ファイルの場合
            if attach_fname:
                # fileに添付ファイルを保存する
                attach_data = part.get_payload(decode=True)
                bucket_source = s3.Bucket(bucket)
                bucket_source.put_object(ACL='private', Body=attach_data,
                                         Key='file' + "/" + attach_fname, ContentType='text/plain')
        #追加--------------------------------                               
        #メール送信
        e = "ミスやで～"
        r = send_email(SRC_MAIL, DST_MAIL, e)
        #ここまで----------------------------

        return 'end'
    except Exception as e:
        print(e)
        print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
        raise e
~~~

# メールを送信して返事が来るか試してみる
とりあえずメール送ります。
![31.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/1f7628d6-a7d7-c841-8c21-d46712f818df.png)

返事が来た！！
![32.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/4b2aaf97-c785-002b-748e-ca87ddc78689.png)

# メールにファイルを添付して送信する
さっき作成したLambdaに追記していきます！

ファイルを添付して送信するときの注意点！！
・メール送信にはclient.send_raw_email()を使用しないと、ファイルを添付できない。
・LambdaではS3から直接ファイルを取得して添付ファイルにすることができない。
    　→Lambdaのtmp/配下に一度保存してからファイルを添付する流れ。

~~~python:sendtemp.py
import boto3
import base64
import email
import urllib.parse
import json
import os
from logging import getLogger, INFO

from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

logger = getLogger(__name__)
logger.setLevel(INFO)

s3 = boto3.resource('s3')

#メアド
SRC_MAIL = "送信元メールアドレス"
DST_MAIL = "送信先メールアドレス"
REGION = "SESのリージョン"

#メール送信
def send_email(source, to, subject):
    client = boto3.client('ses', region_name=REGION)
    #変更-----------------------------------------------------------------------
    msg = MIMEMultipart()
    
    filepath="/tmp/test.csv"
    part = MIMEApplication(open(filepath, "rb").read())
    
    part.add_header("Content-Disposition", "attachment", filename="test.csv")
    msg.attach(part)
    
    part = MIMEText("添付ファイルやで～")
    msg.attach(part)

    response = client.send_raw_email(
        Source=source,
        Destinations=[
            to
        ],
        RawMessage={
            "Data": msg.as_string(),
        },
    )
    #ここまで------------------------------------------------------------------
    
    return response

def lambda_handler(event, context):
    print("--------- logger.info の出力 ---------")
    logger.info(json.dumps(event))
    
    # S3のデータ取得
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    messageid = key.split("/")[1]
    
    try:
        response = s3.meta.client.get_object(Bucket=bucket, Key=key)
        email_body = response['Body'].read().decode('utf-8')
        email_object = email.message_from_string(email_body)

        #メールから添付ファイルを抜き出す
        for part in email_object.walk():
            print("maintype : " + part.get_content_maintype())
            if part.get_content_maintype() == 'multipart':
                continue
            # ファイル名の取得
            attach_fname = part.get_filename()
            print(attach_fname)

            # ファイルの場合
            if attach_fname:
                # fileに添付ファイルを保存する
                attach_data = part.get_payload(decode=True)
                bucket_source = s3.Bucket(bucket)
                bucket_source.put_object(ACL='private', Body=attach_data,
                                         Key='file' + "/" + attach_fname, ContentType='text/plain')
                                         
                #追加-------------------------------------------------
                #tmp配下に保存
                with open("/tmp/test.csv", "a") as attach_data:
                    print("Write data to testFile", file=attach_data)
                #ここまで---------------------------------------------
                
                                         
        #メール送信
        e = "ミスやで～"
        r = send_email(SRC_MAIL, DST_MAIL, e)

        return 'end'
    except Exception as e:
        print(e)
        print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
        raise e
~~~

# 添付ファイルが送信できるか確認する
先ほどと同様にメールを送信してみた。

返信が来た！！！！
![34.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3138153/0dbe2184-8b40-0811-232e-ceae81b65438.png)

# 終わり！！！！！！！！！！
後はLambda上でCSVファイルをいい感じにして（openpyxlとかかな？）記載ミスがあればSESで返信、
正しければS3のfile/配下に格納という動作を付け加えれば、当初やりたかった機能を実装可能かと。
Eventbridge + Lambdaの検証はそこら中に転がっているので今回はパスしました。笑笑
（Azure Functionsなら時間実行とか一瞬やのにな～とか思いながら）

とりあえずpart1~3に分けて技術ブログというものを書いてみました。
初めてのことなので、文章がわかりづらかったり理解が間違っていたりすることがあるかもしれないですが
やり続けないと成長しないと思うのでこれからも少しずつ続けていきます。

これからも何か面白そうなものがあれば検証してみます〇