inboxili-laravel maintained by inboxili
inboxili/inboxili-laravel
Laravel integration for the Inboxili transactional email API: a service provider, a facade, a queued job and a mail transport. Built on inboxili/inboxili. Laravel 10, 11 or 12, PHP 8.1+.
composer require inboxili/inboxili-laravel
The service provider is auto-discovered. Add your key to .env:
INBOXILI_API_KEY=ik_live_...
Create the key in the Inboxili dashboard under Settings, Developer, with the transactional:send scope. Optionally publish the config with php artisan vendor:publish --tag=inboxili-config.
Send with the facade
use Inboxili\Laravel\Facades\Inboxili;
$result = Inboxili::emails()->send([
'to' => $user->email,
'from_email' => 'hello@yourdomain.com', // must be a sender on a verified domain
'subject' => 'Welcome, {{first_name}}',
'html_body' => '<p>Hi {{first_name}}, your account is ready.</p>',
'template_data' => ['first_name' => $user->first_name],
]);
$result->messageId;
Dependency injection
use Inboxili\Client;
public function __construct(private Client $inboxili) {}
$this->inboxili->emails->send([...]);
Queue it
use Inboxili\Laravel\Jobs\SendInboxiliEmail;
SendInboxiliEmail::dispatch([
'to' => $user->email,
'from_email' => 'hello@yourdomain.com',
'template_id' => config('services.welcome_template_id'),
'template_data' => ['first_name' => $user->first_name],
]);
The job runs once. HTTP 429 is already retried inside the SDK. Retrying after a timeout could send a duplicate because the API has no idempotency key, so failures go to your failed-jobs table for you to decide.
Use it as a Laravel mail transport
In config/mail.php:
'mailers' => [
'inboxili' => ['transport' => 'inboxili'],
],
Then MAIL_MAILER=inboxili, and Mailables, notifications (password reset, email verification) and Mail::to() go through Inboxili.
The Inboxili API accepts one recipient and has no CC, BCC, reply-to or attachment fields. The transport does not drop them silently: a message that uses any of them, or has more than one to address, throws a TransportException naming what is unsupported. A text-only message is sent as escaped HTML plus the text part. MAIL_FROM_ADDRESS must be a sender on a verified Inboxili domain.
Error handling
Inboxili\InboxiliException carries status and errorCode (for example 422 / sender_not_verified). Inboxili\ConnectionException means no response was received and the email may or may not have been sent. Through the mail transport both surface as TransportException.
Configuration
| Key | Env | Default |
|---|---|---|
key |
INBOXILI_API_KEY |
none (required) |
base_url |
INBOXILI_BASE_URL |
https://api.inboxili.com/api/v1 |
timeout |
INBOXILI_TIMEOUT |
10 seconds |
max_retries |
INBOXILI_MAX_RETRIES |
2 (HTTP 429 only) |
Webhooks
Verify signatures with the SDK helper, using the raw body and your webhook secret. Exclude the route from CSRF protection.
$ok = \Inboxili\Webhook::verify($request->getContent(), (string) $request->header('X-Inboxili-Signature'), config('inboxili.webhook_secret'));
Testing your app
Swap the client in the container with one that has a fake transport (see the PHP SDK), or use Http-free fakes: $this->app->singleton(\Inboxili\Client::class, fn () => new \Inboxili\Client('test', transport: $fake));.
Development
This package depends on inboxili/inboxili. Until that is on Packagist, develop against a local checkout with a path repository in an untracked composer.local.json, then:
COMPOSER=composer.local.json composer install
vendor/bin/phpunit
Links
Documentation · Laravel guide · Report a vulnerability · MIT licensed