AWS

Laravel6 AWS SES連携 + Bounce, Complaint対応 その②

AWS

 

その①でLaravel + SESのメール認証までできました😊

 

バックナンバー

 

その②ではAWS SESを利用する上で必須となる、Bounce対策を行っていきます。

 

バウンス用のアドレスにメールを送る

  • バウンス
    bounce@simulator.amazonses.com
  • 苦情
    complaint@simulator.amazonses.com

 

 

SNSトピックの作成

 

https://console.aws.amazon.com/sns/v3/home?region=us-east-1#/topics

 

【トピックの作成】をクリックします。

 

名前:SES-Bounce-Notify

と入力します。

【トピックの作成】をクリックします。

 

 

トピックが作成できました😆

ARNの値をメモしてください、SQSの設定で利用します。

 

SQSの作成

  • キュー名:SESBouceMailQueue
  • キューのタイプ:標準キュー

【キューのクイック作成】をクリックします。

 

【アクセス許可】のタブをクリックして、【アクセス許可の追加】をクリックします。

 

  • プリシンパルの項目:全員にチェック
  • アクション:SendMessageにチェック

 

【条件の追加(オプション)】をクリックします。

 

 

aws:SourceArn: <SNSで作成したトピックのarn名>

SNSのARNを入力して、【アクセス許可の追加】をクリックします。

 

 

SESのNotification設定

【Domains】から設定したいドメインをクリックします。

 

 

【Notifications】をクリックします。

 

 

【Edit Configuration】をクリックします。

 

BounceにSNSで作成したトピック名を指定し、【Save Config】をクリックします。

 

 

SNSのサブスクリプション設定

SES-Bounce-Notifyトピックにて、【サブスクリプションの作成】をクリックします。

 

 

  • プロトコル:Amazon SQS
  • エンドポイント:SQSのARN名

【サブスクリプションの作成】をクリックします。

 

Bounceメールのテスト

ユーザ登録APIを叩いてSESにメール送信します。

指定しているメールアドレスはSESのBounceシミュレータであるメールアドレスを指定しるので確実にバウンスされます。

 

SQSを確認する

利用可能なメッセージに1になっています。連携に成功しています。

 

QUEUEの隙を見逃すな!

Laravelから無防備を晒しているSQSのキューの隙をついてキューを首を叩き落とせ。

キューに保存されているバウンスメールアドレス情報をデータベースに登録しろ!

 

SQSに入っているJSONデータ

array(2) {
  [0]=>
  array(5) {
    ["MessageId"]=>
    string(36) "7e976ba9-c57b-40fc-8d9f-d54480e6f99f"
    ["ReceiptHandle"]=>
    string(412) "AAAAAAQEBAeNxxJmY0I8Dmsq3P3hi+DLPgPRFGY5yrjRGlRzkstyXR3c15RTxl+N/nt76wA0hbiTGytazg3tBs1h2BmHACic1CEWUTBYk+wdfI1XLN+sTYSdSIoQ6vd7p+lLVBJbOkeDmZX3S//mHTOmb5VKDOqrAIrefBS8JBrXt9xfHNifsw45dWxIkAyfpLfyMTNF+mbAExB/EMW3RYBHaBqrPg0S0y0zUdxqtT6rTpCt7QqH2cdREdSr8CndRgYIx3lVeNaKRf0p00YhwfOZxD+o8AE8SSL+URrNl/XAsMPl772EVQfqlMe08EsyxVceZd83CjwN01siD5mhzRvdqKu+YtgwNIMXGQsUIVeSKi8ivTeZz45OH4PzDVTGhY7H3l2OcgDyRA0DAvSpVqxMrpKlg=="
    ["MD5OfBody"]=>
    string(32) "e2963132982d2e477a12f3a101959d0a"
    ["Body"]=>
      string(1772) "{
      "Type" : "Notification",
      "MessageId" : "de249a72-c82f-5db8-a6cf-ccf4f08ca5b7",
      "TopicArn" : "arn:aws:sns:us-east-1:xxxxx:SES-Bounce-Notify",
      "Message" : "{
        \"notificationType\":\"Bounce\",
        \"bounce\":{
            \"bounceType\":\"Permanent\",
            \"bounceSubType\":\"General\",
            \"bouncedRecipients\":[{
              \"emailAddress\":\"bounce@simulator.amazonses.com\",
              \"action\":\"failed\",\"status\":\"5.1.1\",
              \"diagnosticCode\":\"smtp; 550 5.1.1 user unknown\"
              }
            ],
            \"timestamp\":\"2020-01-01T06:04:19.046Z\",
            \"feedbackId\":\"0100016f5fb472a7-84ab6e74-AAAAAA-452e-8dc9-d3095016b195-000000\",
            \"remoteMtaIp\":\"18.xxx.yyy.12\",\"reportingMTA\":\"dsn; a48-37.smtp-out.amazonses.com\"},
            \"mail\":{\"timestamp\":\"2020-01-01T06:04:18.000Z\",
            \"source\":\"info@yuutest1.work\",
            \"sourceArn\":\"arn:aws:ses:us-east-1:xxxxx:identity/yuutest1.work\",
            \"sourceIp\":\"14.8.39.32\",
            \"sendingAccountId\":\"925948485307\",
            \"messageId\":\"0100016f5fb47099-3bc5ad88-5102-4544-9d2d-61e70a65b4c1-000000\",
            \"destination\":[
                \"bounce@simulator.amazonses.com\"
            ]
      }
  }",
  "Timestamp" : "2020-01-01T06:04:19.072Z",
  "SignatureVersion" : "1",
  "Signature" : "AAAAAsEygUsmhI13LnAgaAjey134Wen484UD7LZsfQ1iru+RfLlUC5Z4QI3LunRv0ZMOIVbuAYK18n7DL6a5Rl0DvxoDniIIcklvJZAl9347t1oB+heP33uX0ovQesD8/OZBnV7VaCXvDbss1TOugmRj2/EINFDb9Sga8VcLRgwKGh7Y6I6Gd5dTemQsuoT/0V9XUnZfFxmQdFQxepXpDWBFHCRZRCpVBo3k9egqGY4VhgPUPyhMSW4F5DcWk6f5V/OPHq5MCct3v3qJezhE/xx3CisfzOVeER1P+fPc6amn3g3XZK54eYToNMONU6AJeF5P/VlYFn2zMeQ+oSw==",
  "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-6aad65c2f9911b05cd53efda11f913f9.pem",
  "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:xxxxx:SES-Bounce-Notify:25e4fc31-a73d-4c8d-b183-d5ca732e68e8"
}"
    ["Attributes"]=>
    array(4) {
      ["SenderId"]=>
      string(21) "AIDAIT2UOQQY3AUExxxxx"
      ["ApproximateFirstReceiveTimestamp"]=>
      string(13) "1577858659116"
      ["ApproximateReceiveCount"]=>
      string(1) "8"
      ["SentTimestamp"]=>
      string(13) "1577858659116"
    }
  }
  [1]=>
  array(5) {
    ["MessageId"]=>
    string(36) "fa56f0a6-9a9d-44aa-bc17-0f67d928479b"

・・・

これをうまいこと料理しよう。

 

 

コンフィグの設定

標準キューを利用

.env

QUEUE_CONNECTION=sqs
SQS_KEY=<AWS アクセスキーID>
SQS_SECRET=<AWS アクセスキーシークレット>
SQS_PREFIX=https://sqs.ap-northeast-1.amazonaws.com/xxxxx/MyAppMailQueue.fifo
SQS_QUEUE=MyAppMailQueue.fifo
SQS_REGION=ap-northeast-1
SQS_VERSION=2012-11-05
  • QUEUE_CONNECTION=sqsになっています。
  • SQS_PREFIX + SQS_QUEUE = “SQSのキューURL”になるようにしてください😆

 

 

config/queue.php

<?php

return [

    'connections' => [

・・・

        'sqs' => [
            'driver' => 'sqs',
            'key' => env('SQS_KEY'),
            'secret' => env('SQS_SECRET'),
            'prefix' => env('SQS_PREFIX'),
            'queue' => env('SQS_QUEUE'),
            'region' => env('SQS_REGION'),
            'version' => env('SQS_VERSION', '2012-11-05'),
        ],

・・・

 

コンフィグの設定反映

$ php artisan config:clear
$ php artisan config:cache

 

 

※FIFOキューを利用する場合

一応書いておきます。

.env

QUEUE_CONNECTION=sqs-fifo
SQS_KEY=<AWS キーID>
SQS_SECRET=<AWS アクセスキーシークレット>
SQS_PREFIX=https://sqs.ap-northeast-1.amazonaws.com/xxxxx/
SQS_QUEUE=MyAppMailQueue.fifo
SQS_REGION=ap-northeast-1

config/queue.php

        'sqs-fifo' => [
            'driver' => 'sqs-fifo',
            'key'    => env('SQS_KEY'),
            'secret' => env('SQS_SECRET'),
            'queue'  => env('SQS_PREFIX') . env('SQS_QUEUE'),
            'region' => env('SQS_REGION'),
            'group' => 'default',
            'deduplicator' => 'unique',
        ],

コンフィグの設定反映

$ php artisan config:clear
$ php artisan config:cache

 

 

 

 

コマンドを作る

 

$ php artisan make:command registerBounceEmailAddressFromSQS

 

 

app/Http/Console/Commands/registerBounceEmailAddressFromSQS.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;
use Aws\Credentials\Credentials;
use Illuminate\Support\Facades\Log;

class registerBounceEmailAddressFromSQS extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:registerBounceEmailAddressFromSQS';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'AWS SQSからBounceされたメールアドレスを取得してDBに登録します。';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        try{
            // SQSに接続してQUEUEを取得してBouceEmailアドレスをDBに登録
            $prefix = config('queue.connections.sqs.prefix');
            $queue = config('queue.connections.sqs.queue');
            $queue_url = $prefix . $queue;
            $region = config('queue.connections.sqs.region');
            $version = config('queue.connections.sqs.version');
            $credentials = new Credentials(config('queue.connections.sqs.key'), config('queue.connections.sqs.secret'));

            $client = new SqsClient([
                'credentials' => $credentials,
                'region' => $region,
                'version' => $version,         // AWS SQSのバージョン
            ]);

            $receive = [
                'AttributeNames' => ['All'],
                'MessageAttributeNames' => ['All'],
                'MaxNumberOfMessages' => 10,
                'QueueUrl' => $queue_url,
                'WaitTimeSeconds' => 20,
                'VisibilityTimeout' => 60,
            ];

            // キューを監視してキューがあれば受信しキューを削除します。
            while(true){
                $result = $client->receiveMessage($receive);
                $data = $result->get('Messages');

                if($data){
                    foreach($data as $item){
                        // キューの削除
                        $client->deleteMessage([
                            'QueueUrl' => $queue_url,
                            'ReceiptHandle' => $item['ReceiptHandle'],
                            'VisibilityTimeout' => 1000,
                        ]);
                    }
                }
            }
        } catch(AwsException $e){
            // 例外処理
            Log::error('Command - Class ' . get_class() . ' - ' . $e->getMessage());
        }
    }
}

 

コマンドの実行

# php artisan command:registerBounceEmailAddressFromSQ

 

 

SQSのキューは0に変わってるな!

DBにも登録されてるな!

 

はい、できました!😊

お疲れ様でした。

 

 

 

そんなんじゃねぇだろ?

忘れるな!😤

  • バウンスキューをDBに登録すること
  • メール送信時にバウンスされたメールアドレスではないか?確認してからSESでメール送信すること。

これだ。

ぼぅっと生きてんじゃねぇ!🐱

 

バウンスされたメールアドレスを保存するテーブル bounce_emailsを作成

$ php artisan make:migration create_bounceEmails_table

 

database/migrations/xxxxx_create_bounceEmails_table

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class CreateBounceEmailsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('bounce_emails', function (Blueprint $table) {
            $table->bigIncrements('bounce_email_id')->comment('バウンスメールアドレステーブル 主キー');
            $table->string('email')->comment('バウンスされたメールアドレス');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('bounce_emails');
        DB::statement('ALTER TABLE `bounce_emails` auto_increment = 1;');
    }
}

 

app/Http/Models/BounceEmail.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class BounceEmail extends Model
{
    /**
     * @var string プライマリーキー名
     */
    protected $primaryKey = 'bounce_email_id';
}

 

マイグレーションの実行

$ php artisan migrate:fresh

 

キューの受信処理

 

app/Http/Console/Commands/registerBounceEmailAddressFromSQS.php

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Aws\Sqs\SqsClient;
use Aws\Exception\AwsException;
use Aws\Credentials\Credentials;
use Illuminate\Support\Facades\Log;
use App\Models\BounceEmail;

class registerBounceEmailAddressFromSQS extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:registerBounceEmailAddressFromSQS';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'AWS SQSからBounceされたメールアドレスを取得してDBに登録します。';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * SQSからBounceされたメールアドレスを取得してDBに登録してキューを削除
     * - 既に登録されたメッセージidの場合は登録せずにそのまま削除 (SQSは複数同じメッセージを送信する場合がある為)
     *
     * @return void
     */
    public function handle() :void
    {
        try{
            // SQSに接続してQUEUEを取得してBouceEmailアドレスをDBに登録

            $prefix = config('queue.connections.sqs.prefix');
            $queue = config('queue.connections.sqs.queue');
            $queue_url = $prefix . $queue;
            $region = config('queue.connections.sqs.region');
            $version = config('queue.connections.sqs.version');
            $credentials = new Credentials(config('queue.connections.sqs.key'), config('queue.connections.sqs.secret'));

            $client = new SqsClient([
                'credentials' => $credentials,
                'region' => $region,
                'version' => $version,
            ]);

            $receive = [
                'AttributeNames' => ['All'],
                'MessageAttributeNames' => ['All'],
                'MaxNumberOfMessages' => 10,
                'QueueUrl' => $queue_url,
                'WaitTimeSeconds' => 20,
                'VisibilityTimeout' => 60,
            ];

            // キューを監視してキューがあれば受信しDBにバウンスメールを登録し、キューを削除します。
            while(true) {
                $result = $client->receiveMessage($receive);
                $data = $result->get('Messages');

                if($data) {
                    foreach($data as $item){
                        $json_Message_Body = json_decode($item['Body'], true);
                        $sqs_messageId = $json_Message_Body['MessageId'];
                        $json_Message_Body_Message = json_decode($json_Message_Body['Message'], true);
                        $bounceEmailAddress = $json_Message_Body_Message["bounce"]["bouncedRecipients"][0]["emailAddress"];

                        // 既に処理したキューではないか?SQSのメッセージidを確認してから処理する
                        $is_exist_sqs_messageId = BounceEmail::where('sqs_message_id', $sqs_messageId)
                            ->exists();
                        if(!$is_exist_sqs_messageId) {
                            // バウンスメールアドレスを登録
                            $bounceEmail =  new BounceEmail();
                            $bounceEmail->sqs_message_id = $sqs_messageId; // SQSメッセージid
                            $bounceEmail->email = $bounceEmailAddress;     // バウンスメールアドレス
                            $bounceEmail->save();
                        }

                        // キューの削除
                        $client->deleteMessage([
                            'QueueUrl' => $queue_url,
                            'ReceiptHandle' => $item['ReceiptHandle'],
                            'VisibilityTimeout' => 1000,
                        ]);
                    }
                }
            }
        } catch(AwsException $e) {
            // 例外処理
            Log::error('Command - Class ' . get_class() . ' - ' . $e->getMessage());
        }
    }
}

 

バウンスメールアドレスに送信を行い、キューを貯めよう。

そして…

コマンドの実行

# php artisan command:registerBounceEmailAddressFromSQ

ちゃんとデータベースに登録されていますか?

確認しよう。

 

メール送信処理

 

app/Http/Console/Commands/RegisterController.php

<?php

namespace App\Http\Controllers\Api\V1_0;

use App\Http\Controllers\Controller;
use App\Http\Requests\Api\V1_0\RegistUserRequest;
use App\Models\User;
use App\Models\Activation;
use Illuminate\Http\Request;
use Ramsey\Uuid\Uuid;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use App\Mail\ActivationCreated;
use App\Models\UserRole;
use App\Models\Device;
use App\Models\BounceEmail;


class RegisterController extends Controller
{
    private $code;
    private $userGuestRoleId;
    private $now;
    private $activation;

    public function __construct()
    {
        $userRole = new UserRole();
        $this->userGuestRoleId = $userRole->getGuestRoleId();
        $this->now = Carbon::now()->format('Y-m-d H:i:s');
    }
    /**
     * 登録リクエストを受付
     *
     * @param App\Http\Requests\Api\V1_0\RegistUserRequest
     * @return void
     */
    public function register(RegistUserRequest $request): string
    {
        if(!$this->is_bounce($request)) {
            $this->createActivation($request);

            return response()->json([
                'message' => config('mail.message.send_verify_mail')
            ]);
        }
        else {
            // エラー処理
            // 登録希望のメールアドレスが既にバウンスメールリストに登録されている
            return response()->json([
                'code' => config('error.alreadyRegisteredAsBounceEmailAddress.code'),
                'message' => config('error.alreadyRegisteredAsBounceEmailAddress.message')
            ]);
        }

    }

    /**
     * メールアドレスがバウンスメールとして登録されているか、否かを確認
     *
     * @param App\Http\Requests\Api\V1_0\RegistUserRequest
     * @return bool
     */
    public function is_bounce(RegistUserRequest $request): bool
    {
        return BounceEmail::where('email', $request->email)->exists();
    }

    /**
     * アクティベーションコードを生成して認証コードをメールで送信
     *
     * @param App\Http\Requests\Api\V1_0\RegistUserRequest
     * @return void
     */
    private function createActivation(RegistUserRequest $request): void
    {
        $activation = new Activation;
        $activation->user_name = $request->name;
        $activation->email = $request->email;
        $activation->password = bcrypt($request->password);
        $activation->udid = $request->udid;
        $activation->device_os = $request->device_os;
        $activation->device_token = $request->device_token;
        $activation->code = Uuid::uuid4();
        $activation->save();

        Mail::to($activation->email)->send(new ActivationCreated($activation));
    }

    /**
     * メール認証コードを検証してユーザ情報の登録
     *
     * @param Illuminate\Http\Request
     * @return string
     */
    public function verify(Request $request) :string
    {
        $this->code = $request->code;

        // 認証確認
        if (!$this->checkCode($this->code)) {

            // 認証確認エラー処理
            return response()->json(config('error.mailActivationError'));
        } else {
            // ユーザ情報, デバイス情報の登録
            try {
                $retries = (int)3; // トランザクションリトライ回数
                DB::beginTransaction(function() {}, $retries);

                $this->activation = Activation::where('code',$this->code)->first();
                $generalRoleId = $this->userGuestRoleId;
                $user = new User();
                $user->user_name = $this->activation->user_name;
                $user->email = $this->activation->email;
                $user->password = $this->activation->password;
                $user->user_role_id = $generalRoleId;
                $user->save();

                Activation::where('code', $this->code)->update(['email_verified_at' => Carbon::now()]);
                $user_id = $user->user_id;
                $udid = $this->activation->udid;
                $device_os = $this->activation->device_os;
                $device_token = $this->activation->device_token;
                Device::create([
                        'udid' => $udid,
                        'device_os' => $device_os,
                        'device_token' => $device_token
                ]);

                $user->devices()->attach(
                    ['user_id' => $user_id],
                    ['udid' => $udid],
                    ['created_at' => $this->now],
                    ['updated_at' => $this->now]
                );

                DB::commit();

                return response()->json(config('mail.message.add_user_success'));
            } catch (\Illuminate\Database\QueryException $e) {
                // トランザクションでのエラー処理
                DB::rollback();
                Log::error('WEB /users/me/verify - Class ' . get_class() . ' - PDOException Error. Rollback was executed.' . $e->getMessage());

                return response()->json(config('error.databaseTransactionRollback'));
            } catch (\Exception $e) {
                // その他のエラー処理
                DB::rollback();
                Log::error('WEB /users/me/verify - Class ' . get_class() . ' - something went wrong elsewhere.' . $e->getMessage());

                return response()->json(config('error.databaseSomethingWentWrongError'));
            }
        }
    }

    /**
     *  メール認証コードの検証
     *
     *  1. 与えられた認証コードがActivations.codeに存在するか?
     *  2. users.emailが存在しユーザ登録が既に完了したメールアドレスかどうか?
     *  3. 認証コード発行後1日以内に発行された認証コードであるか?
     *
     * @param string $code - メール認証のURLパラメータから取得する認証コード
     * @return boolean
     */
    private function checkCode($code): bool
    {
        $activation = Activation::where('code',$code)->first();
        if (!$activation) {
            return false;
        }

        $activation_email = $activation->email;
        $latest = Activation::where('email',$activation_email)->orderBy('created_at', 'desc')
                                                              ->first();
        $user = User::where('email',$activation_email)->first();
        $activation_created_at = Carbon::parse($activation->created_at);
        $expire_at = $activation_created_at->addDay(1);
        $now = Carbon::now();

        return $code === $latest->code && !$user && $now->lt($expire_at);
    }
}

 

バウンスメールの登録と送信処理がうまくできていたら終わりです😊

お疲れ様でした〜。

 

 

 

 

生殺与奪の権を他人に握らせるな!!

コマンド状態でのキューのポーリングでは作成したコマンドの実行を止めたら、キューの処理が止まってしまう。

そんなことで守ったつもりか?🐱

 

Dockerfileを作成する

 

このphp artisan command:ほにゃららコマンドを実行し続けるワーカーとなるコンテナをこしらえるのだ!

# php artisan command:registerBounceEmailAddressFromSQ

 

Dockerは嫌なんだよな…って人はsupervisordを利用しよう。EC2を1台で動かすならsupervisordでしょう。

私が今回DockerでやるのはAWS ECS Fargateで動かすことが前提で作っているからなのだ🐱

docker-files/php-ses-bounce-queue-listener/Dockerfile

FROM composer:1.9.0
RUN docker-php-ext-install pdo_mysql

WORKDIR /usr/share/nginx/

RUN docker-php-ext-install pdo_mysql
RUN mkdir -p storage/framework/cache/data
RUN chmod -R 755 storage
RUN chown -R www-data:www-data storage

CMD ["php", "artisan", "command:registerEmailProblemFromSQS"]

 

cat docker-compose.yml

---
version: "3.7"
services:

略

  php-ses-bounce-queue-listener:
    build: ./docker-files/php-ses-bounce-queue-listener
    volumes:
      - ./src:/usr/share/nginx:cached
    working_dir: "/usr/share/nginx"
    restart: always
  
略

 

$ docker-compose up -d

 

これでSQSのキューを処理するリスナーが自動起動します😊

 

その③へ

 

Laravel6 AWS SES連携 + Bounce, Complaint対応 その③

 

 

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)