mailin-api/mailin-api-php
PHP client for Brevo's (formerly Sendinblue) transactional email and SMS API. Send messages, manage contacts and lists, and track events with a lightweight wrapper that drops easily into Laravel or any PHP project.
This is the official Sendinblue (formerly Mailin) v2.0 API wrapper — a single, unnamespaced PHP class that speaks to https://api.sendinblue.com/v2.0. It was last released in 2017 and the repo is archived, so treat it as a legacy integration: pin the version, wrap it, and plan an exit to the Brevo v3 API.
Minimal setup:
composer require mailin-api/mailin-api-php
The package predates PSR-4. If Composer does not register the global Mailin class for you, add it to your root composer.json and composer dump-autoload:
"autoload": { "files": ["vendor/mailin-api/mailin-api-php/Mailin.php"] }
Pull the API key from the Sendinblue/Brevo dashboard (SMTP & API → API Keys), store it in .env, and expose it via config/services.php:
// config/services.php
'mailin' => [
'url' => env('MAILIN_URL', 'https://api.sendinblue.com/v2.0'),
'key' => env('MAILIN_API_KEY'),
],
First use case — a transactional welcome email on registration:
$mailin = new Mailin(config('services.mailin.url'), config('services.mailin.key'));
$result = $mailin->send_email([
'to' => ['[email protected]' => 'Jane Doe'],
'from' => ['[email protected]', 'Your App'],
'subject' => 'Welcome aboard',
'html' => view('emails.welcome', ['user' => $user])->render(),
]);
if (($result['code'] ?? null) !== 'success') {
Log::error('Mailin send failed', ['response' => $result]);
}
Where to look first: the Mailin.php class itself. Every public method (send_email, send_sms, create_list, get_account, get_campaigns_v2, …) maps 1:1 to a v2 endpoint and takes a single associative array. The README examples are the only documentation you get — there is no generated SDK reference.
Bind it once, inject it everywhere. Don't new Mailin(...) at call sites. Register a singleton in a service provider and type-hint the wrapper, not the SDK:
// app/Services/MailinService.php
class MailinService
{
public function __construct(private \Mailin $client) {}
public function sendTransactional(array $payload): array
{
$response = $this->client->send_email($payload);
if (($response['code'] ?? null) !== 'success') {
throw new MailinException($response['message'] ?? 'Unknown Mailin error', $response);
}
return $response['data'] ?? [];
}
}
// AppServiceProvider::register()
$this->app->singleton(\Mailin::class, fn () => new \Mailin(
config('services.mailin.url'),
config('services.mailin.key'),
));
Queue everything. The SDK is synchronous cURL with no timeout/retry configuration. A slow Sendinblue response becomes a slow HTTP request in your app. Wrap sends in a queued job (ShouldQueue) and let Laravel's retry/backoff handle transient failures. This is the single biggest day-to-day win.
Build a thin Mailable/Notification bridge. You can't drop this into Laravel's Mail transport without writing a custom Transport implementation; the path of least resistance is a dedicated NotificationChannel or a MailinService::sendTemplate() helper, then call that from notifications and jobs. This keeps the rest of the codebase free of the SDK's array shapes.
Test by binding a fake. Because you injected the wrapper, swap it in tests with a spy that records payloads. Never assert against the real client in feature tests.
Multi-purpose account operations. Beyond send_email, developers commonly use send_sms, create_list/add_to_list for contact syncing, and get_account for health checks on a scheduled command. Each takes a flat associative array; keep payload builders in small helper methods so key typos surface in tests rather than at runtime.
Integration tip: if you only need transactional email, prefer a Laravel-native transport (SMTP or a maintained Brevo/Sendinblue package). Reach for this SDK when you need the v2-specific endpoints (SMS, legacy list/campaign management) that your account still relies on.
Mailin in the root namespace. It will collide with anything else declaring Mailin and can't be autoloaded under PSR-4. Namespacing it (a one-line fork) is often the first thing teams do.['code' => 'failure', 'message' => '…'], not exceptions. Always check the code key and throw your own exception, or errors vanish silently into logs you never wrote.false rather than a structured response. Defensive ?? null checks and null-safe decoding are worth it.default_socket_timeout and cURL defaults at the app level; rely on queued job retries rather than the SDK.to, from, subject, html, text, cc, bcc, attachment. Keys are not validated; a typo yields a generic failure. Builders + tests are your safety net.from address on a verified domain — otherwise the API rejects or deliverability tanks regardless of the code..env, never in config defaults or logs.How can I help you explore Laravel packages today?