Vanessa LozzardoHow to send email with Laravel using the Sendkit API
Set up Sendkit in your Laravel app with the official package. Covers Mailables, notifications, queuing, and error handling.

Laravel already has a solid mail system. You set a driver, configure credentials, and the Mail facade handles everything from there. The problem is usually the provider behind it. SMTP credentials from your hosting company, a free-tier service that throttles you after 100 emails, or an API that requires you to rewrite your mail logic from scratch.
Sendkit plugs into Laravel's existing mail system. You install the package, set two environment variables, and your existing Mailables and notifications work without any code changes. This guide covers the full setup.
Install the package
Pull in the official Laravel package:
composer require sendkit/sendkit-laravelThe package auto-registers its service provider via Laravel's package discovery. No manual provider registration needed.
You'll need an API key from your Sendkit dashboard. Keys start with sk_live_ for production and sk_test_ for testing. The test key behaves identically but doesn't deliver mail, so you can run your test suite without spamming real inboxes.
Configure your environment
Add these two lines to your .env file:
MAIL_MAILER=sendkit
SENDKIT_API_KEY=sk_live_your_api_key_hereThat's it. Laravel's mail system reads MAIL_MAILER to decide which transport to use, and the Sendkit package registers itself as a valid mailer. Your config/mail.php doesn't need any changes unless you want to run Sendkit alongside another mailer (more on that later).
For local development, swap in your test key:
MAIL_MAILER=sendkit
SENDKIT_API_KEY=sk_test_your_test_key_hereThe package also publishes a config file if you want more control:
php artisan vendor:publish --tag=sendkit-configThis creates config/sendkit.php where you can set options like timeout, retry behavior, and the API base URL (useful if you're running against a staging environment).
Send your first email
If you already have Mailables in your app, they'll work immediately. No changes needed:
use App\Mail\WelcomeEmail;
use Illuminate\Support\Facades\Mail;
Mail::to('[email protected]')->send(new WelcomeEmail($user));That WelcomeEmail class you wrote months ago? It still works. The Mail facade routes it through Sendkit's email API instead of whatever you were using before.
If you're starting fresh, generate a new Mailable:
php artisan make:mail OrderShippedThen fill it in:
namespace App\Mail;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class OrderShipped extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public Order $order,
) {}
public function envelope(): Envelope
{
return new Envelope(
from: '[email protected]',
subject: "Order #{$this->order->number} has shipped",
);
}
public function content(): Content
{
return new Content(
view: 'emails.order-shipped',
);
}
}The from address must belong to a verified domain in your Sendkit account. If you skip it, Laravel uses the MAIL_FROM_ADDRESS from your .env, which also needs to be on a verified domain.

Send notifications through Sendkit
Laravel's notification system is separate from Mailables, but it uses the same mail transport under the hood. If you're using the mail channel in your notifications, those go through Sendkit too.
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class InvoicePaid extends Notification
{
use Queueable;
public function __construct(
private float $amount,
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Payment received')
->greeting("Hi {$notifiable->name},")
->line("We received your payment of \${$this->amount}.")
->action('View receipt', url('/receipts'))
->line('Thanks for your business.');
}
}Trigger it the usual way:
$user->notify(new InvoicePaid(29.00));Nothing about the notification code references Sendkit. That's the point. Your mail transport is a configuration detail, not something your application logic should care about.
Queue your emails
Sending email inline (during a web request) is fine for development, but in production it adds latency. A password reset email that takes 200ms to send means the user waits 200ms longer for the response. Queue it instead.
Laravel gives you two options. Either implement ShouldQueue on your Mailable:
use Illuminate\Contracts\Queue\ShouldQueue;
class OrderShipped extends Mailable implements ShouldQueue
{
// everything else stays the same
}Or queue it at the call site:
Mail::to($user)->queue(new OrderShipped($order));Both approaches push the email onto your queue (Redis, SQS, database, whatever you've configured). A queue worker picks it up and sends it through Sendkit in the background.
You can also delay sends:
Mail::to($user)
->later(now()->addMinutes(10), new OrderShipped($order));This is useful for things like follow-up emails after a signup, where you want a gap before the next message.
Make sure your queue worker is running:
php artisan queue:workIf queued emails fail (network timeout, API error), Laravel's queue system handles retries automatically based on your queue config. The Sendkit package returns proper error responses so Laravel knows whether to retry or mark the job as failed.

Handle errors
Most email sends succeed. When they don't, you want to know why. The Sendkit package throws exceptions that map to Laravel's mail error handling.
For inline sends, wrap them in a try/catch:
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log;
try {
Mail::to($user)->send(new OrderShipped($order));
} catch (\Exception $e) {
Log::error('Email send failed', [
'to' => $user->email,
'error' => $e->getMessage(),
]);
// maybe flash a message to the user, or retry later
}For queued emails, define a failed method on your Mailable or set up a global failed job handler:
// In your AppServiceProvider or a dedicated listener
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Queue;
Queue::failing(function (JobFailed $event) {
Log::error('Queued job failed', [
'job' => $event->job->resolveName(),
'exception' => $event->exception->getMessage(),
]);
});Common failure reasons:
- Invalid API key (401). Check your
SENDKIT_API_KEYvalue. - Unverified sender domain (422). Add and verify your domain in the Sendkit dashboard.
- Rate limit hit (429). Back off and retry. The queued approach handles this naturally.
- Validation error (422). Bad email format, missing subject, or missing
fromaddress.
The Sendkit docs have the full list of error codes and what triggers them.
Use multiple mailers
Sometimes you want Sendkit for transactional email but keep another provider for something else. Laravel supports multiple mailers out of the box.
In your config/mail.php, define both:
'mailers' => [
'sendkit' => [
'transport' => 'sendkit',
],
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST'),
'port' => env('MAIL_PORT'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
],
],Then specify which mailer to use per send:
Mail::mailer('sendkit')->to($user)->send(new OrderShipped($order));You could also use Sendkit's SMTP service as your SMTP mailer if you want both the API and SMTP paths going through the same provider. The API path is faster (no SMTP handshake overhead), but SMTP works if you have legacy code that expects it.
SMTP as a fallback
If you don't want to install the package at all, you can use Sendkit as a standard SMTP provider. Set these in your .env:
MAIL_MAILER=smtp
MAIL_HOST=smtp.sendkit.com
MAIL_PORT=587
MAIL_USERNAME=sendkit
MAIL_PASSWORD=sk_live_your_api_key_here
MAIL_ENCRYPTION=tlsYour API key doubles as your SMTP password. This approach works, and you still get delivery tracking (opens, clicks, bounces) in the Sendkit dashboard. The API package gives you a bit more control, but SMTP is a valid path if you prefer fewer dependencies.
Testing
In your test environment, you don't want emails going out at all. Laravel's Mail::fake() still works:
use Illuminate\Support\Facades\Mail;
use App\Mail\OrderShipped;
public function test_order_shipped_email_is_sent(): void
{
Mail::fake();
// ... trigger the action that sends email
Mail::assertSent(OrderShipped::class, function ($mail) use ($order) {
return $mail->order->id === $order->id;
});
}Since Mail::fake() intercepts at the facade level, it never reaches the Sendkit transport. Your tests stay fast and don't depend on network connectivity.
For staging environments where you want to see real emails arrive, use the sk_test_ API key. It processes the email through Sendkit's full pipeline but doesn't deliver to real inboxes. You can inspect the results in the Sendkit dashboard.
What's next
You didn't have to learn a new API or rewrite any mail code. Laravel's mail system already does the right thing. Sendkit just gives it a better transport.
If you're working with Node.js instead of (or alongside) Laravel, check out the Node.js transactional email guide. The concepts are similar, though the SDK looks different.
For the full API reference, including batch sending, template management, and webhook events, see the Sendkit documentation.
Share this article