agirem / clario-laravel
Laravel Mail transport and webhook consumer for the Clario email API.
Requires
- php: ^8.2
- illuminate/http: ^11.0|^12.0
- illuminate/mail: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
- symfony/mailer: ^7.0
- symfony/mime: ^7.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^11.0|^12.0
README
Laravel Mail transport and optional webhook consumer for the Clario email API.
Send with familiar Laravel Mailables:
Mail::to($user)->send(new OtpCodeMail($code));
Requirements
- PHP 8.2+
- Laravel 11 or 12
- Clario API access (active trial, Solo, or Pro)
- API key with
emails:send
Installation
composer require agirem/clario-laravel
Publish config (optional):
php artisan vendor:publish --tag=clario-config
Configuration
.env:
MAIL_MAILER=clario CLARIO_API_KEY=clario_live_... # Optional when Laravel From matches a Clario address: # CLARIO_MAIL_ADDRESS_ID=01HX... CLARIO_BASE_URL=https://clario-mail.com/api/v1
config/mail.php:
'clario' => [ 'transport' => 'clario', ],
Sender resolution
The transport sends either mail_address_id or from to the Clario API, in this order:
- Message header
X-Clario-Mail-Address-Id→mail_address_id - Map
config('clario.mail_addresses')[from@email]→mail_address_id - Default
CLARIO_MAIL_ADDRESS_ID→mail_address_id - Laravel / Symfony
Fromaddress →from(API resolves the mailbox)
Display name is configured on the Clario address, not via Laravel from.name.
Idempotency (important for queued mail)
Every send includes an Idempotency-Key. Prefer a stable business key on critical mailables:
public function headers(): Headers { return new Headers( text: [ 'X-Clario-Idempotency-Key' => 'otp-'.$this->user->id.'-'.$this->codeId, ], ); }
If omitted, the transport hashes the rendered message (stable across worker retries of the same content).
Attachments
Attachments are sent as multipart/form-data. Limits match the API: max 10 files, 5 MB each, allowed MIME types (PDF, JPEG/PNG/GIF, Word, Excel, CSV, plain text).
Webhooks (optional)
CLARIO_WEBHOOKS_ENABLED=true CLARIO_WEBHOOK_SECRET=whsec_... CLARIO_WEBHOOK_PATH=clario/webhook
Point your Clario Developer webhook to https://your-app.test/clario/webhook.
Events dispatched:
Clario\Laravel\Webhooks\Events\EmailQueuedEmailSent,EmailDelivered,EmailBounced,EmailFailedEmailComplained,EmailOpened,EmailClicked,EmailReceived
Event::listen(EmailDelivered::class, function (EmailDelivered $event) { // $event->emailId, $event->data['metadata'], $event->eventId });
Signatures use Clario-Signature: t=…,v1=… (HMAC-SHA256 of timestamp.rawBody). Invalid signatures return 401.
The route uses the api middleware group (no CSRF).
Metadata
public function headers(): Headers { return new Headers(text: [ 'X-Clario-Metadata-order_id' => (string) $this->order->id, ]); }
Exceptions
Clario\Laravel\Exceptions\ClarioTransportException exposes:
statusCode,errorCode,requestIdretryable— useful for queue$this->release()vs fail
Non-retryable examples: pro_plan_required, sender_not_allowed, quota exceeded, idempotency_conflict.
Testing
composer install
composer test
License
MIT