Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Sendgrid Notification Channel Laravel Package

swiftmade/laravel-sendgrid-notification-channel

Send email notifications through SendGrid’s API in Laravel. This channel supports dynamic templates, attachments, categories, custom data, and per-notification configuration, giving you a reliable alternative to SMTP for transactional mail.

View on GitHub
Deep Wiki
Context7

Getting Started

Since this is a first assessment, treat v2.10.0 as the baseline. The release itself is low-risk: it widens the framework constraint to include Laravel 13 and fixes CI. There are no breaking changes, no new public API, and no deprecations — upgrading from v2.9.0 is a normal minor bump.

Install and wire up

composer require swiftmade/laravel-sendgrid-notification-channel
php artisan vendor:publish --tag=sendgrid-config   # if you want a local config file

Add your credentials to .env:

SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxxxx

Then run php artisan config:cache (or config:clear in local) so the key is actually picked up.

Where to look first

  1. README.md — the usage examples are short and accurate enough to copy.
  2. src/SendGridChannel.php — this is the whole integration surface; it shows exactly how the message is turned into an API call and where exceptions bubble from.
  3. src/SendGridMessage.php — the fluent builder and the toArray()/payload contract. Read this before debugging any 400 from SendGrid.
  4. config/sendgrid.php (published) — confirm the key name it reads matches your .env.

First use case

A single transactional notification — welcome email, password reset, receipt — sent to one user. Add the channel to via() and implement toSendGrid():

use Swiftmade\SendGridNotificationChannel\SendGridChannel;

public function via(object $notifiable): array
{
    return [SendGridChannel::class];
}

public function toSendGrid(object $notifiable): SendGridMessage
{
    return (new SendGridMessage())
        ->from(config('mail.from.address'), config('mail.from.name'))
        ->subject('Welcome aboard')
        ->content('text/plain', 'Thanks for signing up.');
}

Send it with $user->notify(new WelcomeNotification()) and confirm delivery in the SendGrid Activity Feed rather than only in logs — the package returns little on success, so the dashboard is your source of truth.

Implementation Patterns

Array payload vs. fluent builder. SendGridMessage accepts a raw SendGrid v3 payload array, and also exposes fluent helpers for the common fields (from, subject, content, templateId, dynamicTemplateData, personalization, attachment, category, customArg, sendAt). Use the fluent builder for simple transactional mail; drop to the raw array when you need a shape the helpers don't cover (multiple personalizations with different substitutions, asm unsubscribe groups, mail_settings). The array is passed through essentially verbatim, so the SendGrid v3 Mail Send docs become your reference.

Dynamic templates are the main reason to use this channel over plain Mail. Store copy and layout in SendGrid, send handles and merge data from Laravel:

return (new SendGridMessage())
    ->from(config('mail.from.address'))
    ->templateId(config('services.sendgrid.templates.receipt'))
    ->dynamicTemplateData([
        'first_name' => $notifiable->first_name,
        'order_total' => $order->formatted_total,
    ]);

This lets marketing/localisation edit copy without a deploy. Keep template IDs in config/ (not hardcoded) so staging and production can diverge.

Per-recipient routing. Implement routeNotificationForSendGrid() on the notifiable to control the address, mirroring routeNotificationForMail():

public function routeNotificationForSendGrid(): string
{
    return $this->billing_email;
}

Useful for team/account notifiables or for sending to a different address than the login email.

On-demand notifications. For a one-off recipient that isn't a model:

Notification::route('sendgrid', '[email protected]')->notify(new InviteNotification($invite));

Queue it, always. Make notifications implement ShouldQueue. If you need transactional mail to survive a Redis outage while marketing mail queues, implement viaConnections() and return a separate connection per channel. Set $tries and backoff deliberately — see the retry gotcha below.

Combine channels during migration. A common rollout is dual-sending: [MailChannel::class, SendGridChannel::class] behind a feature flag, so you can compare deliverability in SendGrid's stats before cutting the default mailer over.

Testing. Notification::fake() plus assertSentTo($user, WelcomeNotification::class, fn ($n) => ...) covers the wiring, but it does not exercise the payload. Add one or two tests that call toSendGrid() directly and assert on the resulting array, so template ID and dynamicTemplateData regressions are caught. For end-to-end tests, Http::fake(['api.sendgrid.com/*' => Http::response(['message' => 'success'], 202)]) keeps the suite offline and fast.

Integration tip — local and CI. Swapping MAIL_MAILER=log does nothing here; this channel talks to HTTP directly. Locally, either fake the HTTP client in a service provider, or use a null/decorator channel bound in local so notifications don't leave the machine. SendGrid's own sandbox mode (via mail_settings.sandbox_mode) is useful when you want to validate a real payload against the real API without delivering.

Gotchas and Tips

A missing API key fails at send time, not boot time. Nothing validates the credential at container resolution, so a typo in SENDGRID_API_KEY surfaces as an exception thrown from inside the queued notification job — often minutes later. Add a smoke test or an artisan command that sends one message to an internal address after deploy.

Config caching bites hard. If you publish the config and later change .env, you must re-run config:cache. This is the single most common "it works locally, not in staging" cause with this package.

toSendGrid() must return a message or array. Returning null (easy to do if you early-return when the user has opted out) produces an opaque error rather than a graceful skip. To conditionally suppress, return an empty via() array instead of a null message.

SendGrid 400s are almost always shape problems, not value problems. The two recurring ones: no personalization with a to address (the channel will usually build one from the notifiable, but custom payloads can override that), and content missing when no templateId is set. Read SendGridMessage::toArray() output — log it once in staging — and diff it against the v3 spec. SendGrid's error body names the offending field; make sure your exception handler logs $e->getResponseBody() and not just the message.

templateId and subject/content don't mix well. When a template is set, subject comes from the template (subject or the dynamic data), and supplying content can yield a 400 or silently ignored fields depending on the template type. Pick one mode per notification.

Retries can duplicate real email. A queued job that times out after SendGrid accepted the request will retry and send again. Keep $tries low (2–3) for anything user-visible, and where duplicates are unacceptable use a deterministic customArg / custom_args correlation ID so you can dedupe downstream, or gate on an idempotency record you write before dispatching.

Notification::fake() assertions need the class, not a string. assertSentTo($user, WelcomeNotification::class) works; asserting on 'sendgrid' will not match how the channel is keyed. When asserting payload contents, use the closure form and call toSendGrid() on the passed notification.

Rate limits and throughput. SendGrid returns 429 with a X-RateLimit-Reset header. For bulk/fan-out sends, throttle the job (middleware(new RateLimited('sendgrid')) with a limiter you define, or ->delay() per recipient) rather than relying on retry-with-backoff to smooth things out — backoff will still burn your retry budget.

Extension points. If you outgrow the builder, two clean options: (a) extend SendGridMessage with domain-specific helpers (->receipt($order)) and keep using the channel, or (b) write a thin custom channel that injects the official sendgrid/sendgrid SDK directly, which gives you access to features the payload-passthrough approach doesn't model (suppression checks, dynamic template validation, the Email Activity API). Option (a) covers most teams; only reach for (b) when you need API calls beyond Mail Send.

Version pinning. v2.10.0 is additive — Laravel 13 in the support matrix plus CI fixes. Pin ^2.10 and upgrade freely; no code changes are required from v2.9. Just make sure your Laravel version is actually in the supported range, since the widened constraint may pull in framework versions your other packages don't yet support.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast