datomatic / laravel-hubspot-email-notification-channel
Laravel Channel to save email and notifications on Hubspot Email
Package info
github.com/datomatic/laravel-hubspot-email-notification-channel
pkg:composer/datomatic/laravel-hubspot-email-notification-channel
Fund package maintenance!
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.4
- illuminate/notifications: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- mockery/mockery: ^1.6
- orchestra/testbench: ^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0|^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
This package makes it easy to log notifications to Hubspot Email Engagement V3 with Laravel >= 12.x
Contents
Installation
You can install the package via composer:
composer require datomatic/laravel-hubspot-email-notification-channel
Setting up the HubspotEmail service
Create a Private App in Hubspot and copy its access token. Hubspot API keys were sunset on November 30th 2022 and are no longer accepted.
Configure your Hubspot API on .env
HUBSPOT_ACCESS_TOKEN=XXXXXXXX HUBSPOT_OWNER_ID=XXX # an Hubspot owner id to save as email creator
To publish the config file to config/hubspot.php run:
php artisan vendor:publish --provider="Datomatic\LaravelHubspotEmailNotificationChannel\HubspotEmailServiceProvider"
This will publish a file hubspot.php in your config directory with the following contents:
// config/hubspot.php return [ 'access_token' => env('HUBSPOT_ACCESS_TOKEN'), 'hubspot_owner_id' => env('HUBSPOT_OWNER_ID'), 'company_email_associations' => true, 'retry' => [ 'times' => env('HUBSPOT_RETRY_TIMES', 3), 'sleep_milliseconds' => env('HUBSPOT_RETRY_SLEEP_MILLISECONDS', 11 * 1000), ], ];
Hubspot enforces its rate limit over a ten second window, so the default retry waits eleven seconds
between attempts. Three attempts means a failing call can block for over twenty seconds, which matters
inside a queued job: lower HUBSPOT_RETRY_TIMES to 1 to disable retrying altogether.
Usage
You can now use the channel in your via() method inside the Notification class.
Email notification
Your Notification class must have toMail method. The package accepts: MailMessage lines notifications, MailMessage view notifications and Markdown mail notifications.
Data stored on Hubspot:
- Hubspot Contact Id => The Notifiable Model must implement Datomatic\LaravelHubspotEmailNotificationChannel\Contracts\HasHubspotContact
- Send at timestamp
- subject
- mail text (the html of the email or the toHubspotTextMail method of notification)
Example
Notification example
use Datomatic\LaravelHubspotEmailNotificationChannel\HubspotEmailChannel; use Illuminate\Notifications\Notification; class OrderConfirmation extends Notification { ... public function via($notifiable) { return ['mail', HubspotEmailChannel::class]]; } public function toMail($notifiable) { $message = (new MailMessage) ->subject(__('order.order_confirm', ['code' => $this->order->code])); return $message->view( 'emails.order', [ 'title' => __('order.order_confirm', ['code' => $this->order->code]), 'order' => $this->order ] ); } //Optional text method public function toHubspotTextMail($notifiable):string { return 'text of message to put on hubspot'; } ... }
Send text version of html email
An example of use of toHubspotTextMail method is to send the text version of the email.
use Soundasleep\Html2Text; class OrderConfirmation extends Notification { ... public function toHubspotTextMail(mixed $notifiable): string { return Html2Text::convert($this->toMail($notifiable)->render()); } }
Model example
namespace App\Models; use Datomatic\LaravelHubspotEmailNotificationChannel\Contracts\HasHubspotContact; use Illuminate\Notifications\Notification; class User extends Authenticatable implements HasHubspotContact { ... public function getHubspotContactId(Notification $notification): int|string|null { return $this->hubspot_contact_id; } ... }
Returning null skips logging the notification to Hubspot.
Handling a stale contact id
Since the 2026-09 write validation, Hubspot rejects an association to a contact id it cannot resolve
instead of silently accepting it. That surfaces as a HubspotObjectNotFound, which carries the
decoded error body:
use Datomatic\LaravelHubspotEmailNotificationChannel\Exceptions\HubspotObjectNotFound; try { $user->notify(new OrderConfirmation($order)); } catch (HubspotObjectNotFound $e) { // ["CONTACT=838442890479 is not valid"] logger()->warning('Stale Hubspot contact', $e->invalidObjectIds()); $user->update(['hubspot_contact_id' => null]); }
HubspotObjectNotFound extends CouldNotSendNotification and also exposes payload(), category()
and correlationId(). Note that the email object is created before the association is attempted, so a
rejected association leaves an orphaned email in Hubspot.
Dynamic Contact Owner
use Datomatic\LaravelHubspotEmailNotificationChannel\HubspotEmailChannel; use Illuminate\Notifications\Notification; class PersonalMessage extends Notification { ... public function via($notifiable) { return ['mail', HubspotEmailChannel::class]]; } public function toMail($notifiable) { $message = (new MailMessage) ->subject(__('messages.personal_subject')) ->from($this->employee->email, $this->employee->name) ->metadata('hubspot_owner_id', $this->employee->hubspot_owner_id); return $message->view( 'messages.personal', [ 'title' => __('messages.personal_welcome', ['recipient' => $notifiable->name]), 'employee' => $this->employee ] ); } ... }
Changelog
Please see CHANGELOG for more information what has changed recently.
Testing
$ composer test
Security
If you discover any security related issues, please email info@albertoperipolli.com instead of using the issue tracker.
Contributing
Please see CONTRIBUTING for details.
Credits
License
The MIT License (MIT). Please see License File for more information.