-
-
Save Dozorengel/25d51576815d21ca5684c15d48ecc5a9 to your computer and use it in GitHub Desktop.
Sending newsletters via Sendgrid API (Laravel)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
use Illuminate\Support\Facades\Log; | |
use GuzzleHttp\Client; | |
use App\Models\Newsletter; | |
use App\Models\Member; | |
/** | |
* Service for working with SendGrid. | |
*/ | |
class SendgridService | |
{ | |
public function __construct(array $suppression_groups, int $sender_id) | |
{ | |
$this->client = new Client(); | |
$this->headers = ['Authorization' => 'Bearer ' . config('sendgrid.api_key')]; | |
$this->suppression_groups = $suppression_groups; | |
$this->sender_id = $sender_id; | |
} | |
/** | |
* Retrieve all list names and ids from SendGrid. | |
* | |
* @return array $lists Contact lists | |
*/ | |
public function getSendgridLists(): array | |
{ | |
Log::info('Start getSendgridLists()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/lists'; | |
try { | |
$response = $this->client->get($url, [ | |
'headers' => $this->headers, | |
]); | |
$content = $response->getBody()->getContents(); | |
$remote_lists = json_decode($content, true); | |
foreach ($remote_lists as $items) { | |
$lists = []; | |
foreach ($items as $item) { | |
$lists[$item['name']] = $item['id']; | |
} | |
return $lists; | |
} | |
} catch (\Throwable $th) { | |
$this->logError($th, 'GET'); | |
} | |
} | |
/** | |
* Determine unsubscribed members to exclude from SendGrid. | |
* | |
* @return string Set of members ids to delete | |
*/ | |
public function getUnsubscribedMembersIds(): string | |
{ | |
Log::info('Start getUnsubscribedMembersIds()'); | |
$unsubscribed_members = Member::select('email')->where('status', Member::STATUS_UNSUBSCRIBED)->get(); | |
$unsubscribed_ids = ''; | |
foreach ($unsubscribed_members as $member) { | |
$searched_id = $this->search($member->email); | |
if ('' === $searched_id) { | |
continue; | |
} | |
$unsubscribed_ids .= $searched_id . ','; | |
} | |
return rtrim($unsubscribed_ids, ','); | |
} | |
/** | |
* Delete unsubscribed members from SendGrid. | |
* | |
* @param string $query_ids Members ids | |
* @return void | |
*/ | |
public function deleteMembers(string $query_ids): void | |
{ | |
Log::info('Start deleteMembers()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/contacts?ids=' . $query_ids; | |
try { | |
$this->client->delete($url, [ | |
'headers' => $this->headers, | |
]); | |
} catch (\Throwable $th) { | |
$this->logError($th, 'DELETE'); | |
} | |
} | |
/** | |
* Create/update members data to SendGrid. | |
* | |
* @param array $data Users data formed for SendGrid | |
* @return void | |
*/ | |
public function storeMembers(array $data): void | |
{ | |
Log::info('Start storeMembers()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/contacts'; | |
try { | |
$this->client->put($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($data), | |
]); | |
} catch (\Throwable $th) { | |
$this->logError($th, 'PUT'); | |
} | |
} | |
/** | |
* Cut a string if it exceeds a predefined length. | |
* | |
* @param string $str | |
* @param int $length Default 25, if longer SendGrid throws an error | |
* @return string | |
*/ | |
public function trimString(string $str, int $length = 25) | |
{ | |
return mb_strlen($str) > $length | |
? mb_strimwidth(trim($str), 0, $length) | |
: $str; | |
} | |
/** | |
* Send emails manually via SendGrid. | |
* | |
* @param Newsletter $newsletter | |
* @param array $emails Recipients | |
* @return void | |
*/ | |
public function sendManualNewsletter( | |
Newsletter $newsletter, | |
array $recipient_emails_list | |
): void { | |
$html = view('email.main')->with('newsletter', $newsletter)->render(); | |
$html = str_replace('<h4>', '<h4 style="font-weight: bold;">', $html); | |
$html = str_ireplace('*|MC_PREVIEW_TEXT|*', $newsletter->lead, $html); | |
$html = str_ireplace('*|MC:SUBJECT|*', $newsletter->subject, $html); | |
$html = str_ireplace('*|UNSUB|*', '<%asm_group_unsubscribe_raw_url%>', $html); | |
$html = str_ireplace('*|UPDATE_PROFILE|*', '<%asm_preferences_raw_url%>', $html); | |
Log::info('[SendGrid] sending started. emails count(' . count($recipient_emails_list) . ')'); | |
foreach ($recipient_emails_list as $recipient_email) { | |
$recipient_email = trim($recipient_email); | |
echo "--------------------------------------------------------\n"; | |
echo '[SENDING]: ' . $recipient_email . "\n"; | |
$email = new \SendGrid\Mail\Mail(); | |
$email->addTo($recipient_email); | |
$email->setFrom(config('mailchimp.from'), 'The Company'); | |
$email->setSubject($newsletter->subject); | |
$email->addContent('text/html', $html); | |
$sendgrid = new \SendGrid(getenv('SENDGRID_KEY')); | |
try { | |
$response = $sendgrid->send($email); | |
echo $response->statusCode() . "\n"; | |
print_r($response->headers()); | |
echo $response->body() . "\n"; | |
Log::info("[SendGrid] sent to $recipient_email"); | |
} catch (\Exception $e) { | |
echo 'Caught exception: ', $e->getMessage(), "\n"; | |
Log::error("[SendGrid] ($recipient_email) Caught exception: " . $e->getMessage()); | |
} | |
echo "--------------------------------------------------------\n"; | |
} | |
Log::info('[SendGrid] sending end.'); | |
} | |
/** | |
* Get first single send entity ID. | |
* | |
* It is needed to get the first entity specifically, | |
* as the first entity must contain a short mock title | |
* to avoid restriction of max 100 characters when duplicating | |
* | |
* @return string $id | |
*/ | |
public function getFirstSingleSendId(): string | |
{ | |
Log::info('Start getFirstSingleSendId()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/singlesends'; | |
try { | |
$response = $this->client->get($url, [ | |
'headers' => $this->headers, | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
$content_data = json_decode($content, true); | |
$count = count($content_data['result']) - 1; | |
return $content_data['result'][$count]['id']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'GET'); | |
} | |
} | |
/** | |
* Create a single send (similar to campaign). | |
* | |
* Now it duplicates an existing single send entity. | |
* | |
* @param string $single_send_id ID of the existing single send instance | |
* @return string $id ID of the duplicated single send instance | |
*/ | |
public function createSingleSend(string $single_send_id): string | |
{ | |
Log::info('Start createSingleSend()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/singlesends/' . $single_send_id; | |
try { | |
$response = $this->client->post($url, [ | |
'headers' => $this->headers, | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
$content_data = json_decode($content, true); | |
return $content_data['id']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'POST'); | |
} | |
} | |
/** | |
* Get ID of transactional template. | |
* | |
* This method gets the ID of the latest entity specifically, | |
* though we could get any existing record. | |
* | |
* @return string $id | |
*/ | |
public function getTemplateId(): string | |
{ | |
Log::info('Start getTemplateId()'); | |
$url = 'https://api.sendgrid.com/v3/templates?generations=dynamic'; | |
try { | |
$response = $this->client->get($url, [ | |
'headers' => $this->headers, | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
$content_data = json_decode($content, true); | |
return $content_data['templates'][0]['id']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'GET'); | |
} | |
} | |
/** | |
* Create a transactional template. | |
* | |
* An element that can be shared among more than one endpoint | |
* definition. | |
* | |
* Just in case method as only one template is currently using. | |
* | |
* @param string $name Title | |
* @param string $generation legacy/dynamic | |
* @return string $id Transactional template id | |
*/ | |
public function createTemplate( | |
string $name, | |
string $generation = 'dynamic' | |
): string { | |
Log::info('Start createTemplate()'); | |
$url = 'https://api.sendgrid.com/v3/templates'; | |
$data = [ | |
'name' => $name, | |
'generation' => $generation, | |
]; | |
try { | |
$response = $this->client->post($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($data), | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
$content_data = json_decode($content, true); | |
return $content_data['id']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'POST'); | |
} | |
} | |
/** | |
* Get lists from newsletter, where it is needed to send. | |
* | |
* @param Newsletter $newsletter | |
* @param array $sendgrid_lists All lists from SendGrid | |
* @return array $lists | |
*/ | |
public function getNewsletterLists( | |
Newsletter $newsletter, | |
array $sendgrid_lists | |
): array { | |
Log::info('Start getNewsletterLists()'); | |
$lists = []; | |
foreach ($newsletter->lists as $list_name) { | |
if (array_key_exists($list_name, $sendgrid_lists)) { | |
$lists[] = [ | |
'name' => $list_name, | |
'id' => $sendgrid_lists[$list_name], | |
]; | |
} | |
} | |
return $lists; | |
} | |
/** | |
* Get template title as joined lists names and newsletter subject. | |
* | |
* @param Newsletter $newsletter | |
* @param array $newsletter_lists | |
* @return string | |
*/ | |
public function getTemplateTitle( | |
Newsletter $newsletter, | |
array $newsletter_lists | |
): string { | |
Log::info('Start getTemplateTitle()'); | |
$template_title = ''; | |
foreach ($newsletter_lists as $list) { | |
$template_title .= '[' . $list['name'] . ']'; | |
} | |
$template_title .= ' ' . $newsletter->subject; | |
return $this->trimString($template_title, 50); | |
} | |
/** | |
* Create a new transactional template version. | |
* | |
* A new version of the predefined template in SendGrid. | |
* | |
* @param Newsletter $newsletter | |
* @param array $list | |
* @return self | |
*/ | |
public function createTemplateVersion( | |
Newsletter $newsletter, | |
string $template_title, | |
string $template_id | |
): self { | |
Log::info('Start createTemplateVersion()'); | |
$url = "https://api.sendgrid.com/v3/templates/$template_id/versions"; | |
$html = view('email.main')->with('newsletter', $newsletter)->render(); | |
$html = str_replace('<h4>', '<h4 style="font-weight: bold;">', $html); | |
$html = str_ireplace('*|MC_PREVIEW_TEXT|*', $newsletter->lead, $html); | |
$html = str_ireplace('*|MC:SUBJECT|*', $newsletter->subject, $html); | |
$html = str_ireplace('*|UNSUB|*', '<%asm_group_unsubscribe_raw_url%>', $html); | |
$html = str_ireplace('*|UPDATE_PROFILE|*', '<%asm_preferences_raw_url%>', $html); | |
$data = [ | |
'template_id' => $template_id, | |
'active' => 1, | |
'name' => str_slug($template_title), | |
'html_content' => $html, | |
'subject' => $newsletter->subject, | |
]; | |
Log::info('DATA', [json_encode([ | |
'template_id' => $template_id, | |
'active' => 1, | |
'name' => str_slug($template_title), | |
'subject' => $newsletter->subject, | |
])]); | |
try { | |
$this->client->post($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($data), | |
]); | |
return $this; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'POST'); | |
} | |
} | |
/** | |
* Update a single send with an active template version. | |
* | |
* This is a get-ready method to send the newsletter. | |
* @return self | |
*/ | |
public function updateSingleSend( | |
Newsletter $newsletter, | |
string $template_title, | |
string $single_send_id, | |
array $newsletter_lists, | |
string $list_ids | |
): self { | |
Log::info('Start updateSingleSend()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/singlesends/' . $single_send_id; | |
$suppression_group_id = $this->suppression_groups[$newsletter_lists[0]['name']]; | |
$html = view('email.main')->with('newsletter', $newsletter)->render(); | |
$html = str_replace('<h4>', '<h4 style="font-weight: bold;">', $html); | |
$html = str_ireplace('*|MC_PREVIEW_TEXT|*', $newsletter->lead, $html); | |
$html = str_ireplace('*|MC:SUBJECT|*', $newsletter->subject, $html); | |
$html = str_ireplace('*|UNSUB|*', '<%asm_group_unsubscribe_raw_url%>', $html); | |
$html = str_ireplace('*|UPDATE_PROFILE|*', '<%asm_preferences_raw_url%>', $html); | |
$data = [ | |
'name' => $template_title, | |
'send_to' => [ | |
'list_ids' => [$list_ids], | |
], | |
'email_config' => [ | |
'suppression_group_id' => $suppression_group_id, | |
'sender_id' => $this->sender_id, | |
'html_content' => $html, | |
'subject' => $newsletter->subject, | |
], | |
]; | |
try { | |
$this->client->patch($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($data), | |
]); | |
return $this; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'PATCH'); | |
} | |
} | |
/** | |
* Send a ready-to-send newsletter immediately. | |
* | |
* @param string $single_send_id ID of single send instance to send | |
* @return string 'scheduled' if success, as 'triggered' is too early to get | |
*/ | |
public function startSingleSend(string $single_send_id): string | |
{ | |
Log::info('Start startSingleSend()'); | |
$url = "https://api.sendgrid.com/v3/marketing/singlesends/$single_send_id/schedule"; | |
$data = ['send_at' => 'now']; | |
try { | |
$response = $this->client->put($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($data), | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
$content_data = json_decode($content, true); | |
return $content_data['status']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'PUT'); | |
} | |
} | |
/** | |
* Get string of newsletter list ids. | |
* @param array $newsletter_lists | |
* @return string | |
*/ | |
public function getListIdsToString(array $newsletter_lists): string | |
{ | |
Log::info('Start getListIdsToString()'); | |
$list_ids = ''; | |
foreach ($newsletter_lists as $list) { | |
$list_ids .= $list['id'] . ','; | |
} | |
return rtrim($list_ids, ','); | |
} | |
/** | |
* @param string $id Entity ID | |
* @return array Entity | |
*/ | |
public function singleSendEntity(string $id): array | |
{ | |
Log::info('Start singleSendEntity()'); | |
$url = "https://api.sendgrid.com/v3/marketing/singlesends/$id"; | |
try { | |
$response = $this->client->get($url, [ | |
'headers' => $this->headers, | |
]); | |
$body = $response->getBody(); | |
$content = $body->getContents(); | |
return json_decode($content, true); | |
} catch (\Throwable $th) { | |
$this->logError($th, 'GET'); | |
} | |
} | |
/** | |
* Search a subscriber by the email given. | |
* | |
* @param string $email | |
* @return string $member_id | |
*/ | |
private function search(string $email): string | |
{ | |
Log::info('Start search()'); | |
$url = 'https://api.sendgrid.com/v3/marketing/contacts/search'; | |
$body = [ | |
'query' => "primary_email LIKE '" . $email . "%'", | |
]; | |
try { | |
$response = $this->client->post($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($body), | |
]); | |
$content = $response->getBody()->getContents(); | |
$remote_lists = json_decode($content, true); | |
if (empty($remote_lists['result'])) { | |
return ''; | |
} | |
return $remote_lists['result'][0]['id']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'POST'); | |
} | |
} | |
/** | |
* Log and print out the error. | |
* | |
* @param \Throwable $th | |
* @param string $method | |
* @return void | |
*/ | |
private function logError(\Throwable $th, string $method): void | |
{ | |
Log::info('Start logError()'); | |
$error = 'Cannot send ' . $method . ' request to SendGrid'; | |
echo "\n" . $error . "\n"; | |
echo $th . "\n"; | |
Log::error($error . "\n" . $th); | |
exit; | |
} | |
/** | |
* Versions of the transactional template. | |
* | |
* @param string $template_id | |
* @return array Template versions | |
*/ | |
public function templateVersions(string $template_id): array | |
{ | |
Log::info('Start templateVersions()'); | |
$url = 'https://api.sendgrid.com/v3/templates/' . $template_id; | |
$body = [ | |
'template_id' => $template_id | |
]; | |
try { | |
$response = $this->client->get($url, [ | |
'headers' => $this->headers, | |
'body' => json_encode($body), | |
]); | |
$content = $response->getBody()->getContents(); | |
$versions = json_decode($content, true); | |
return $versions['versions']; | |
} catch (\Throwable $th) { | |
$this->logError($th, 'GET'); | |
} | |
} | |
/** | |
* Delete a transactional template version. | |
* | |
* @param string $template_id | |
* @param string $version_id | |
* @return void | |
*/ | |
public function templateVersionDelete( | |
string $template_id, | |
string $version_id | |
): void { | |
Log::info('Start templateVersionDelete()'); | |
$url = 'https://api.sendgrid.com/v3/templates/' . $template_id; | |
$url .= '/versions/' . $version_id; | |
try { | |
$this->client->delete($url, [ | |
'headers' => $this->headers, | |
]); | |
} catch (\Throwable $th) { | |
$this->logError($th, 'DELETE'); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment