AWS日記25 (AWS SES)

# はじめに
今回は AWS SES を試します。
メールを受信し、Webページ上で確認できる機能を作ります。
SESの機能を利用するにあたり[リージョンの制限](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/regions.html#region-receive-email)があった為、バージニア北部(us-east-1)リージョンで作成しました。

[Lambda関数・SAMテンプレート]
(https://github.com/tanaka-takurou/serverless-ses-page-go)



# 準備
[AWS SAM の準備をします](https://qiita.com/tanaka_takurou/items/cae7c19fc3aaf0031abb)

[AWS SESの資料]
[Amazon Simple Email Service 開発者ガイド](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/Welcome.html)

# 作業手順の概略
 - [ドメイン登録](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/receiving-email-getting-started-before.html)
 - [ドメイン検証](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/receiving-email-getting-started-verify.html)
 - [サーバーレスアプリケーションのデプロイ](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-deploying.html) (今回作成した AWS SAM テンプレートは[こちら](https://github.com/tanaka-takurou/serverless-ses-page-go/blob/master/template.yml))
 - 管理コンソールの[Rule Sets](https://console.aws.amazon.com/ses/home?region=us-east-1#receipt-rules:)設定画面からRule Set の アクティベート
 
※ アクティベートのダイアログ
 ![SES_Management_Console.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586707/1a8f017a-9c15-d05b-0752-1eb9abadc7e7.jpeg)


# AWS SAM テンプレート作成

AWS SAM テンプレートで API-Gateway , Lambda, AWS SES, DynamoDB, S3 の設定をします。

[参考資料]
[AWS SAM テンプレートを作成する](https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/tutorial-lambda-sam-template.html)

### AWS SESの設定は以下の部分
```yaml
  ReceiptRuleSet:
    Type: AWS::SES::ReceiptRuleSet
    Properties:
      RuleSetName: !Ref ReceiptRuleSetName
  ReceiptRule1:
    Type: AWS::SES::ReceiptRule
    Properties:
      RuleSetName: !Ref ReceiptRuleSetName
      Rule:
        Name: !Ref ReceiptRuleName1
        Enabled: true
        ScanEnabled: true
        TlsPolicy: !Ref TlsPolicy
        Actions:
          - S3Action:
              BucketName: !Ref EmailBucket
  ReceiptRule2:
    Type: AWS::SES::ReceiptRule
    Properties:
      RuleSetName: !Ref ReceiptRuleSetName
      After: !Ref ReceiptRule1
      Rule:
        Name: !Ref ReceiptRuleName2
        Enabled: true
        ScanEnabled: true
        TlsPolicy: !Ref TlsPolicy
        Actions:
          - LambdaAction:
              FunctionArn: !GetAtt ReceiptFunction.Arn

```
※ メールを受信した際に、S3に保存しLambda関数を呼び出す設定にしました。


# Lambda関数作成

※ Lambda関数は aws-lambda-go を利用しました。

### SESからメールデータを受け取り、DynamoDBに件名などのデータを入力する処理

```golang
func HandleRequest(ctx context.Context, sesEvent events.SimpleEmailEvent) error {
	for _, record := range sesEvent.Records {
		ses := record.SES
		err := putMailInfo(ctx, ses.Mail.CommonHeaders.To[0], ses.Mail.CommonHeaders.Date, ses.Mail.MessageID, ses.Mail.CommonHeaders.From[0], ses.Mail.CommonHeaders.Subject)
		if err != nil {
			log.Println(err)
			log.Printf("CommonHeaders %+v", ses.Mail.CommonHeaders)
		}
	}

	return nil
}

func putMailInfo(ctx context.Context, to string, date string, file string, from string, subject string) error {
	item := MailInfoData {
		To: to,
		Date: date,
		File: file,
		From: from,
		Subject: subject,
	}
	av, err := attributevalue.MarshalMap(item)
	if err != nil {
		return err
	}
	err = put(ctx, os.Getenv("TABLE_NAME"), av)
	if err != nil {
		return err
	}
	return nil
}

```


### S3からメールデータを取得し、メール本文を抜き出す処理
```golang
func getMailBody(ctx context.Context, objectKey string)(string, error) {
	if len(objectKey) == 0 {
		return "", fmt.Errorf("Error: %s", "No ObjectKey.")
	}
	if s3Client == nil {
		s3Client = s3.NewFromConfig(getConfig(ctx))
	}
	input := &s3.GetObjectInput{
		Bucket: aws.String(os.Getenv("BUCKET_NAME")),
		Key:    aws.String(objectKey),
	}
	res, err := s3Client.GetObject(ctx, input)
	if err != nil {
		return "", err
	}

	rc := res.Body
	defer rc.Close()
	tmpData, err := ioutil.ReadAll(rc)
	if err != nil {
		log.Println(err)
		return "", err
	}
	if len(string(tmpData)) < 1 {
		return "", fmt.Errorf("Error: %s", "Empty Mail Body.")
	}

	strNormalized := regexp.MustCompile("\r\n").ReplaceAllString(string(tmpData), "\n")
	rawMailLines := regexp.MustCompile(`\n\s*\n`).Split(strNormalized, -1)
	if len(rawMailLines) < 2 {
		return "", fmt.Errorf("Error: %s", "Empty Mail Body.")
	}
	return rawMailLines[1], nil
}

```

# 終わりに
AWS SES でメールを受信できるよう設定し、Webページ上からメールを確認出来るようにしました。
AWS SES を利用しメールの送信も行える為、今後試そうと思います。

<details><summary>参考資料</summary><div>
[[作業記録] 続・Cloud IdentityでGCPの組織をつくる (Amazon SESでメールを受信する)](https://qiita.com/Hikosaburou/items/333e3b6f7623dcb32ed8)

[Amazon SESでRule Setが動作しない（送信側で「Requested action not taken: mailbox unavailable」となる）](https://qiita.com/r-wakatsuki/items/c1fcbbf0aef01fa9222c)

[AWSでメールを受信したらLambda Functionをキックし作業を自動化する仕組みを作る](https://developers.cyberagent.co.jp/blog/archives/24908/)

[Amazon SES を使用した E メールの受信](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/receiving-email.html)

[AWS::SES::ReceiptRule](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ses-receiptrule.html)

[aws-lambda-go/events/ses.go](https://github.com/aws/aws-lambda-go/blob/e5da494987442caa3749213d5fc56ea6e440bf77/events/ses.go)