
もくじ
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;
}
}
}

