もくじ
SendGrid謹製のライブラリインストール
$ composer require sendgrid/sendgrid
サンプルコード
ドキュメントと、ここのサンプルコードを参考にしながら作っていくと良い🐱
personalized処理
Util作ったった
<?php namespace App\Services\Mail; use App\Util\SendGrid\SendGrid; class MailService { public function __construct( SendGrid $util_sendgird ) { $this->util_sendgrid = $util_sendgrid; } /** * SendGridによるメール配信 * * @param array $data * @return void */ public function sendMailsBySendGrid(array $data): void { $this->util_sendgrid->setMailData($data); $this->util_sendgrid->sendMails(); } }
.env
SENDGRID_API_KEY=xxxxxx
$ php artisan config:clear
<?php namespace App\Util\SendGrid; use App\Exceptions\SendGridApiException; use Dotenv\Exception\ValidationException; use Illuminate\Support\Carbon; use SendGrid\Mail\To; class SendGrid { private $_mail_data = []; private $_validator; private $_sendgrid_email; private $_chunked_emails_array = []; const SENDGRID_SEND_ONCE_REQUEST_LIMIT = 1000; public function __construct() { $this->validator = app()->make(\Illuminate\Validation\Factory::class); $this->sendgrid = new \SendGrid(getenv('SENDGRID_API_KEY')); } /** * 最初にデータをセット * * @param array $data * @param bool $is_test_flag * @return void */ public function setMailData(array $data, ?bool $is_test_flag = false): void { $this->_testData($data, $is_test_flag); $this->_mail_data = $data; // 1,000件単位で1,000メールアドレス/1リクエストに分ける為に1,000件で1配列ずつに分割 $this->_chunked_emails_array = array_chunk($this->_mail_data['emails'], self::SENDGRID_SEND_ONCE_REQUEST_LIMIT); } /** * バリデーション * * @param array $data * @param bool $is_test_flag * @return void */ private function _testData(array $data, bool $is_test_flag): void { $rules = [ // 一斉送信 送信宛先メールアドレス 'emails' => [ 'required', 'array', ], // 差出元アドレス 'from_address' => [ 'required', 'string', ], // 差出人名 'from_name' => [ 'required', 'string', ], // 件名 'subject' => [ 'required', 'string', ], // 本文 'mail_body' => [ 'required', 'string', ], // SendGrid側の配信リクエストid 'category' => [ 'required', 'string', ], ]; // 予約リクエストid if (isset($data['batch_id']) && !empty($data['batch_id'])) { $rules['batch_id'] = [ 'string', ]; } // 予約配信 指定時刻 if (isset($data['delivery_datetime']) && !empty($data['delivery_datetime'])) { $rules['delivery_datetime'] = [ 'date_format:Y-m-d H:i:s', ]; } // テスト配信の場合 統計情報に含めないのでcategoryをチェックしない if ($is_test_flag) { unset($rules['category']); } try { $this->_validator = $this->validator->make($data, $rules); $this->_validator->validate(); } catch (ValidationException $e) { \Log::error('Caught Exception: ' . $e->getMessage()); throw $e; } catch (\Throwable $t) { \Log::error('Caught Throwable: ' . $t->getMessage()); throw $t; } } /** * SendGridへメール送信リクエスト実行 * * @return void * * @see 個人情報保護処理 https://sendgrid.kke.co.jp/blog/?p=12490 */ public function sendMails(): void { try { foreach ($this->_chunked_emails_array as $emails) { // 1リクエスト単位でインスタンス生成 $this->_sendgrid_email = app()->make(\SendGrid\Mail\Mail::class); // 個人情報保護 foreach ($emails as $email) { $personalization = app()->make(\SendGrid\Mail\Personalization::class); $personalization->addTo(new To($email)); $this->_sendgrid_email->addPersonalization($personalization); } $this->_setHeader(); // SendGridにリクエスト送信 $response = $this->sendgrid->send($this->_sendgrid_email); $body = json_decode($response->body(), true); // 2xxでなかったらエラー if (strpos($response->statusCode(), "2") !== 0) { throw $this->_getSendGridApiException($body); } } } catch (SendGridApiException $t) { \Log::error('Caught SendGridApiException: ' . $response->body()); throw $t; } catch (\Throwable $t) { \Log::error('Caught exception: ' . $response->body()); throw $t; } } /** * メールヘッダーのセット * * @return void */ private function _setHeader(): void { // カテゴリーのセット if (array_key_exists('category', $this->_mail_data) && !empty($this->_mail_data['category'])) { $this->_sendgrid_email->addCategory($this->_mail_data['category']); } // batch idがある場合は予約セット if (array_key_exists('batch_id', $this->_mail_data) && !empty($this->_mail_data['batch_id'])) { $this->_sendgrid_email->setBatchId($this->_mail_data['batch_id']); // 送信予定日時セット $dt = Carbon::parse($this->_mail_data['delivery_datetime']); $unix_delivery_datetime = (int) $dt->format('U'); $this->_sendgrid_email->setGlobalSendAt($unix_delivery_datetime); } // 差出人アドレス, 差出人名セット $this->_sendgrid_email->setFrom($this->_mail_data['from_address'], $this->_mail_data['from_name']); // 件名セット $this->_sendgrid_email->setSubject($this->_mail_data['subject']); // テキストメール用に本文をセット $this->_sendgrid_email->addContent("text/plain", $this->_mail_data['mail_body']); // HTMLメール用に本文をセット $this->_sendgrid_email->addContent( "text/html", $this->_mail_data['mail_body'] ); } /** * 予約配信用 batch idをSendGridから取得 * * @return string */ private function _createBatchId(): string { try { $response = $this->sendgrid->client->mail()->batch()->post(); $body = json_decode($response->body(), true); if (strpos($response->statusCode(), "2") !== 0) { throw $this->_getSendGridApiException($body); } if (array_key_exists('batch_id', $body) && !empty($body['batch_id'])) { return $body['batch_id']; } } catch (SendGridApiException $t) { \Log::error('Caught SendGridApiException: ' . $response->body()); throw $t; } catch (\Throwable $t) { \Log::error('Caught exception: ' . $response->body()); throw $t; } } /** * 予約配信リクエスト * * @return string */ public function reserve(): string { $this->_mail_data['batch_id'] = $this->_createBatchId(); $this->sendMails(); return $this->_mail_data['batch_id']; } /** * 有効なbatch idかチェックする * * @param string $batch_id * @return void */ public function validateBatchId(string $batch_id): void { try { $response = $this->sendgrid->client->mail()->batch()->_($batch_id)->get(); $body = json_decode($response->body(), true); if (strpos($response->statusCode(), "2") !== 0) { throw $this->_getSendGridApiException($body); } } catch (SendGridApiException $t) { \Log::error('Caught SendGridApiException: ' . $response->body()); throw $t; } catch (\Throwable $t) { \Log::error('Caught exception: ' . $response->body()); throw $t; } } /** * 予約配信キャンセル(一時停止) * * @param string $batch_id * @return void */ public function cancelReservation(string $batch_id): void { try { $request_body = [ 'batch_id' => $batch_id, 'status' => "pause" ]; $response = $this->sendgrid->client->user()->scheduled_sends()->post($request_body); $body = json_decode($response->body(), true); if (strpos($response->statusCode(), "2") !== 0) { throw $this->_getSendGridApiException($body); } } catch (SendGridApiException $t) { \Log::error('Caught SendGridApiException: ' . $response->body()); throw $t; } catch (\Throwable $t) { \Log::error('Caught exception: ' . $response->body()); throw $t; } } /** * SendGridApiException取得 * * @param ?array * @return ?SendGridApiException */ private function _getSendGridApiException(?array $body = []): SendGridApiException { if (!empty($body) && array_key_exists('errors', $body)) { return new SendGridApiException(500, $body['errors']); } // これは呼ばれない想定 return new SendGridApiException(500, []); } /** * Category単位で統計情報取得 * * @param string $category * @param string $aggregated_by day|month|week * @param string $start_date Y-m-d ... 2021-08-21 * @param string $end_date Y-m-d * @return array * * @see https://sendgrid.kke.co.jp/docs/API_Reference/Web_API_v3/Stats/categories.html * @see https://github.com/sendgrid/sendgrid-php/blob/main/examples/categories/categories.php */ public function getStatByCategories( array $categories, string $aggregated_by, string $start_date, string $end_date ): array { $query_params = [ 'start_date' => $start_date, 'end_date' => $end_date, 'aggregated_by' => $aggregated_by, 'categories' => $categories, ]; try { $response = $this->sendgrid->client->categories()->stats()->get(null, $query_params); $body = json_decode($response->body(), true); // 2xxでなかったらエラー if (strpos($response->statusCode(), "2") !== 0) { throw $this->_getSendGridApiException($body); } return $body; } catch (SendGridApiException $t) { \Log::error('Caught SendGridApiException: ' . $response->body()); throw $t; } catch (\Throwable $t) { \Log::error('Caught exception: ' . $response->body()); throw $t; } } }