railsware/mailtrap-php
Official Mailtrap PHP SDK for Laravel and plain PHP apps. Send transactional email, manage templates and contacts, and integrate Mailtrap’s testing sandbox and email API workflows through a lightweight, framework-agnostic client.
mailtrap/mailtrap-php is the official Mailtrap API SDK, not a full Laravel mail transport. In a Laravel app it lives alongside your normal mailer and covers what SMTP can't: sandbox inspection, bulk/batch sending, contacts, templates, and now inbound email (rules + threads).
composer require mailtrap/mailtrap-php (3.16.0 is additive — safe bump from 3.15.x).config/services.php:$this->app->singleton(MailtrapClient::class, fn () => MailtrapSdk::client(
new Config(
app()->environment('production')
? new BearerAuthCredentials(config('services.mailtrap.api_token'))
: new BearerAuthCredentials(config('services.mailtrap.sandbox_token')),
app()->environment('production') ? null : (int) config('services.mailtrap.inbox_id'),
)
));
First use case: send one templated transactional email from a tinker session or an artisan command against the sandbox, then confirm it appears in the sandbox inbox. That single loop validates credentials, inbox ID, and template UUID wiring before you build anything else. Look first at the Email builder (Email::buildWithTemplate/buildWithHtml/buildWithText) and the sandbox/sending split — almost every early bug is one of those two.
Environment-driven client. Bind one singleton that switches sandbox vs. sending by environment, so controllers and jobs never know which one they're talking to. Keeps test code identical to production code.
Transactional mail via templates. Build the message, don't hand-assemble HTML:
$email = Email::buildWithTemplate($templateUuid)
->subject('Your receipt')
->from(new Address('[email protected]', 'Billing'))
->to(new Address($user->email, $user->name))
->customVariables(['order_id' => $order->id, 'total' => $order->formatted_total]);
$mailtrap->send($email);
Wrap this in a queued job with $tries/backoff so a Mailtrap 5xx or 429 doesn't fail the user request.
Bulk/batch sending. For digests and newsletters, use the batch builder to send up to 500 personalised messages in one API call rather than looping send() — dramatically fewer round trips and cleaner rate-limit behaviour. Chunk larger audiences into batches.
Template API (new in 3.16.0). The paginated /api/templates endpoints are the interesting part for Laravel teams: you can now treat Mailtrap templates as versioned artefacts. Two patterns worth adopting:
resources/mailtrap/*.html and stores the returned UUIDs in config or DB.Because the endpoints are paginated, always iterate pages rather than assuming a single call returns everything:
foreach ($mailtrap->templates()->list() as $template) { /* first page */ }
// iterate subsequent pages until exhausted; cache the result
Check the generated API reference for the exact paginator/iterator method names on these endpoints before wiring them in.
Inbound email (new in 3.16.0). Forward rules let you route inbound mail to an HTTP endpoint or address; threads search lets you pull a conversation by participants/subject. That's a credible path to a support-inbox feature without a second ESP: receive via Mailtrap, persist the thread ID, and reply through the sending API so the thread stays coherent.
Integration tip: keep the SDK behind a thin MailtrapMailer-style wrapper class in your app. Every call site then goes through one place you can stub, log, and later swap — the SDK's objects are otherwise easy to leak into controllers.
Mail::fake() will not see these sends. The SDK talks to the HTTP API directly, bypassing Laravel's mail manager, so mail events, Mail::assertSent, and log-based assertions don't apply. Test by faking your wrapper class, or point tests at the sandbox and assert on the sandbox API.from domain must be verified on the sending domain, or the request fails validation regardless of token scope.customVariables vs template merge fields are distinct concepts — a variable that renders as blank in the delivered mail is usually the wrong one of the two.Config object and the injectable HTTP client are the two seams worth using; wrap them in your own container bindings so you can swap transports in tests without touching call sites.^3.16 is a comfortable constraint — but pin the minor if you're adopting the new template/inbound endpoints in a deploy pipeline, since their surface is still young.How can I help you explore Laravel packages today?