【PHP】Amazon SESを使った添付ファイル付きメールを送信する

# はじめに
この記事ではAmazon SESを利用してPHPでメール送信する方法について説明します。
またAWSやSESについての説明や導入方法については様々な記事がありますので、本記事では省きます。


# 簡易メールの送信（SendEmail）
最も簡単なメール送信で、送信元アドレス・送信先アドレス（複数可）・件名・本文を設定することでメール送信できます。
## Request Parameters
__Destination__
　・ 送信先のメールアドレス(配列)
__Message__
　・ メール本文
　・ メール件名
__Source__
　・ 送信元のメールアドレス

その他にもBCC送信などもあるようなので必要な方は[公式サイト](https://docs.aws.amazon.com/ja_jp/ses/latest/APIReference/API_SendEmail.html)の確認をお願いします。

## 実装：テキストメールの場合

```php5:Textメール
use Aws\Ses\SesClient;

class MailUtil
{
    public static function sendMail() {
        // Amazon SES クライアントのインスタンス生成
        $client = new SesClient([
            'region' => env("AWS_SES_REGION"),   // SESのリージョン
            'version' => 'latest',
            'credentials' => [
                'key' => env("AWS_KEY"),
                'secret'=> env("AWS_SECRET")]
        ]);

　　　　　$fromAddress = "testFrom@test.com";
　　　　　$toAddressList[] = "testTo@test.com";
　　　　　$subject = "SESメールの送信";
　　　　　$text = "SESメールの実装方法について";

        // 送信元アドレス・送信先アドレス（Array）、件名、本文をリクエストパラメータに代入
        $request = [];
        $request['Source'] = $fromAddress;
        $request['Destination']['ToAddresses'] = $toAddressList;
        $request['Message']['Subject']['Data'] = $subject;
        $request['Message']['Body']['Text']['Data'] = $text;

        // 送信する
        $client->sendEmail($request);
    }
}
```

## 実装：HTMLメールの場合
リクエストパラメータを下記のように設定するだけで可能となります。

```php5:HTMLメール
        $request = [];
        $request['Source'] = $fromAddress;
        $request['Destination']['ToAddresses'] = $toAddressList;
        $request['Message']['Subject']['Data'] = $subject;
        $request['Message']['Body']['Html']['Data'] = $text;
        $request['Message']['Body']['Html']['Charset'] = 'iso-2022-jp';
```

[AWS SDK for PHP を使用して E メールを送信する](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-using-sdk-php.html)


# 添付ファイル付きメールの送信(SendRawEmail)
SendEmailに対してSendRawEmailは高度なメールカスタマイズをすることができます。

## 最終的なソースコード

```php5
use Aws\Ses\SesClient;
use Carbon;

class MailUtil
{
    public static function sendAttachedMail($toAddrList, $fromAddr, $subject, $body, $data)
    {
        // sendEmailと同様
        $client = new SesClient([
            'region' => env("AWS_SES_REGION"),
            'version' => 'latest',
            'credentials' => [
                'key' => env("AWS_KEY"),
                'secret' => env("AWS_SECRET")
            ]
        ]);
        
        # 添付ファイル（今回はwindowsをメインで想定していたのでSJIS-winにエンコードしてます）
        $attachFileData['data']     = mb_convert_encoding($data, 'SJIS-win', 'UTF-8');
        $attachFileData['filename'] = Carbon::now()->format('Ymd') . '.csv';
        $attachFileData['mimetype'] = 'text/csv';

        // RawMessageの作成
        $msg = self::createRawMessage($toAddrList, $fromAddr, $subject, $body, $attachFileData);

        $client->sendRawEmail([
            'Source'       => $fromAddress,
            'Destinations' => [$toAddressList],
            'RawMessage'   => [
                'Data' => $msg,
            ],
        ]);
    }

    /**
     * 添付ファイル付きメール用のRawMessageを作成する
     */
    private static function createRawMessage($to, $from, $subject, $body, $filedata)
    {
        if (isset($filedata['filename']) && isset($filedata['data']) && isset($filedata['mimetype'])) {
            $boundaryStr = uniqid(rand());

            $message = "To: " . $to . "\n";
            $message .= "From: " . $from . "\n";
            $message .= "Subject: " . $subject = str_replace("\r", "", $subject) . "\n";
            $message .= "MIME-Version: 1.0\n";
            $message .= 'Content-Type: multipart/mixed; boundary="' . $boundaryStr . '"';
            $message .= "\n\n";
            $message .= "--" . $boundaryStr . "\n";
    　　　　　$message .= "Content-Type: text/html; charset=iso-2022-jp";
            $message .= "\n";
            $message .= "Content-Transfer-Encoding: 7bit\n";
            $message .= "Content-Disposition: inline\n";
            $message .= "\n";
            $message .= $body;
            $message .= "\n";
            $message .= "\n";
            $message .= "--" . $boundaryStr . "\n";
            $message .= 'Content-Type: ' . $filedata['mimetype'] . '; name="' . $filedata['filename'] . '"';
            $message .= "\n";
            $message .= "Content-Transfer-Encoding: base64\n";
            $message .= 'Content-Disposition: attachment; filename="' . $filedata['filename'] . '"';
            $message .= "\n\n";
            $message .= base64_encode($filedata['data']);
            $message .= "\n";
        } else {
            Log::warning('添付ファイルデータが設定されていません');
        }
    }       
}
```


https://github.com/aws/aws-sdk-php/issues/1295#issuecomment-307517135

## 解説
### boundary
メッセージヘッダやコンテンツといったメールの各パートは、boundary で分離されます。
boundary は、各パートの開始と終了を示す文字列で、これらを使ってメッセージ本文、または今回のような添付ファイルを分けて、設定を行なっていきます。

### ヘッダー
ヘッダーには送信先、送信元、件名、MIME-Ver、コンテンツタイプを設定します。
今回はヘッダー・本文・添付ファイルを設定するため、メッセージのコンテンツタイプに`multipart/mixed`を設定します。これを設定することで各パートを別々に扱う必要があることがわかります。

```php
    $message = "To: " . $to . "\n";
    $message .= "From: " . $from . "\n";
    $message .= "Subject: " . $subject = str_replace("\r", "", $subject) . "\n";
    $message .= "MIME-Version: 1.0\n";
    $message .= 'Content-Type: multipart/mixed; boundary="' . $boundaryStr . '"';
    $message .= "\n\n";
```

### メッセージ本文
文字コードに関しては、古いメーラーを使用したユーザーが想定されるためUTF-8ではなく`iso-2022-jp`を選択しましたが、最近はUTF-8でも問題ないかと思います。

```php
    $message .= "Content-Type: text/html; charset=iso-2022-jp";
    $message .= "\n";
    $message .= "Content-Transfer-Encoding: 7bit\n";
    $message .= "Content-Disposition: inline\n";
    $message .= "\n";
    $message .= $body;
```
__Content-Disposition__
・ inline
　メール本文のようなWebページの一部またはWebページとして表示可能である場合はinlineを設定します。

・ attachment
　ダウンロード可能な添付ファイルの場合に、`Content-Disposition: attachment; filename="hoge.xxx"`といった形式で記述します。

### 添付ファイル

```php
    // mimetype:'text/csv'
    $message .= "--" . $boundaryStr . "\n";
    $message .= 'Content-Type: ' . $filedata['mimetype'] . '; name="' . $filedata['filename'] . '"';
    $message .= "\n";
    $message .= "Content-Transfer-Encoding: base64\n";
    $message .= 'Content-Disposition: attachment; filename="' . $filedata['filename'] . '"';
    $message .= "\n\n";
    $message .= base64_encode($filedata['data']);
```
 - Content-Type<br>
添付ファイルの種類で今回はCSVファイルを指定
 - Content-Disposition<br>
添付ファイルのためattachmentをセットし、添付ファイル名を指定
 - Content-Transfer-Encoding<br>
添付ファイルのエンコードに使用されるスキーム。添付ファイルでは、ほとんどの場合この値は base64指定

# 補足説明
## 送信結果の判別
AWS SESではメール送信結果を受け取ることができます。
しかし受け取り可能なのは送信失敗（バウンス処理、迷惑メール）となった場合のみで成功通知は受け取ることができません。

|種別|結果判別可否|
|:--|:-:|
|送信成功|△|
|バウンス処理|◯|
|迷惑メール|◯|

そのため送信後はしばらく待って失敗通知が来ていなければ、おそらく送信成功したのだと判断する必要があり注意が必要となります。

# 参考
- [Amazon SES API を使用して raw Eメールを送信する
](https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/send-email-raw.html)
- [SendRawEmail](https://docs.aws.amazon.com/ja_jp/ses/latest/APIReference/API_SendRawEmail.html)
- [Content-Typeの一覧
](https://qiita.com/AkihiroTakamura/items/b93fbe511465f52bffaa)