Send transactional email from Laravel with an HTTPS email API — controllers, jobs, and env config without SMTP.
Short answer: From Laravel, send transactional email with an HTTP client from a controller, listener, or queued job. Store the API key in .env, never in Blade. With Notify, POST https://notify.cx/api/email/send using header x-api-key and JSON fields to, from, subject, message.
So when someone asks How do I send emails from a Laravel app?, the modern default is not “configure MAIL_MAILER=smtp and hope the host allows port 587.” It is “call an email API the same way you call Stripe.”
Laravel Mail vs an email API
Laravel’s mail system (Mailable, notifications, mailers) is excellent when you want Symfony Mailer abstractions. Many teams still point that stack at SMTP:
MAIL_HOST,MAIL_PORT,MAIL_USERNAME,MAIL_PASSWORDdrift per environment- Shared hosts and some PaaS setups make SMTP painful
- Provider bounce webhooks and send logs live outside Laravel’s log channel
You have two clean options:
- Keep Laravel notifications/Mailables and point a custom mail transport / HTTP mailer at your provider
- Skip the mailer for transactional sends and use
Http::directly (what this article shows)
For password resets, receipts, and invites, a small HTTP helper is usually enough. You can always wrap it later.
Honest provider map
| Need | Reach for |
|---|---|
| Minimal API, flat pricing, send + webhooks + logs | Notify (Free 1k / Pro $10·10k / Scale $50·100k) |
| Ecosystem around React Email / Node SDK habits | Resend |
| Deliverability-first brand | Postmark |
| AWS-native cost at huge volume | SES (accept IAM/SNS and possible sandbox waits) |
Best for Notify: product/SaaS Laravel apps that need transactional mail without ESP bloat.
Not for: newsletters, drip marketing, or drag-and-drop campaign builders.
Install nothing special
Laravel’s HTTP client is enough:
# .env
NOTIFY_API_KEY=your_api_key_here
NOTIFY_FROM=noreply@your-verified-domain.com
// config/services.php
return [
// ...
'notify' => [
'key' => env('NOTIFY_API_KEY'),
'from' => env('NOTIFY_FROM', 'noreply@your-verified-domain.com'),
'url' => 'https://notify.cx/api/email/send',
],
];
Helper class
<?php
// app/Services/NotifyMailer.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use RuntimeException;
class NotifyMailer
{
public function send(string $to, string $subject, string $message, ?string $from = null): array
{
$response = Http::withHeaders([
'x-api-key' => config('services.notify.key'),
'Content-Type' => 'application/json',
])->post(config('services.notify.url'), [
'from' => $from ?? config('services.notify.from'),
'to' => $to,
'subject' => $subject,
'message' => $message,
]);
if ($response->failed()) {
throw new RuntimeException(
'Notify send failed: '.$response->status().' '.$response->body()
);
}
return $response->json() ?? [];
}
}
message accepts HTML or plain text. Render a Blade view to a string when you want templates without a marketing ESP:
$html = view('emails.welcome', ['name' => $user->name])->render();
app(NotifyMailer::class)->send($user->email, 'Welcome', $html);
Controller example
<?php
// app/Http/Controllers/WelcomeEmailController.php
namespace App\Http\Controllers;
use App\Services\NotifyMailer;
use Illuminate\Http\Request;
class WelcomeEmailController extends Controller
{
public function __invoke(Request $request, NotifyMailer $mailer)
{
$data = $request->validate([
'email' => ['required', 'email'],
'name' => ['nullable', 'string'],
]);
$name = $data['name'] ?? 'there';
$mailer->send(
$data['email'],
'Welcome',
"<p>Hi {$name},</p><p>Thanks for signing up.</p>"
);
return response()->json(['ok' => true]);
}
}
Queue it (recommended)
User-facing requests should not wait on network I/O when you can avoid it:
<?php
// app/Jobs/SendTransactionalEmail.php
namespace App\Jobs;
use App\Services\NotifyMailer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendTransactionalEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public string $to,
public string $subject,
public string $message,
) {}
public function handle(NotifyMailer $mailer): void
{
$mailer->send($this->to, $this->subject, $this->message);
}
}
Dispatch:
SendTransactionalEmail::dispatch(
$user->email,
'Reset your password',
'<p><a href="'.$url.'">Reset password</a></p>'
);
Auth / password reset note
Laravel’s built-in password reset notifications use the mailer. Two approaches:
- Keep Laravel’s flow and configure a mailer that ultimately hits your provider
- Own the token table / signed URL yourself and call
NotifyMailerfrom your reset action
For greenfield apps, (2) is often clearer. For apps deep into MustVerifyEmail and notification classes, (1) may be less disruptive — just do not leave production on a flaky SMTP host if you can avoid it.
Domain verification
Production custom from addresses need a verified domain (SPF/DKIM) in Notify. Until then, use sandbox/dashboard testing. Docs: domain verification.
Webhooks
When you care about bounces, point a Laravel route at Notify webhooks and update suppressions or alert Slack. Free plan has no webhooks; Pro and Scale do (see pricing).
FAQ
How do I send emails from a Laravel app?
Use Laravel’s HTTP client (or Guzzle) from a service class or queued job. With Notify, POST to https://notify.cx/api/email/send with x-api-key and to / from / subject / message. Keep the key in .env.
Should I replace Laravel Mail entirely?
Not necessarily. For new transactional paths, a direct HTTP helper is fine. Keep Mailables if the team already depends on them — just avoid fragile SMTP as the transport when an API is available.
Does Notify have a Laravel package?
You do not need one. Http:: plus the snippet above is the intended integration style. No MCP server either — one HTTP call.
How does this compare to SES in Laravel?
Laravel can use SES via the AWS SDK / Symfony bridge. That is reasonable if you already operate SES. New SES accounts often start sandboxed with a production-access review that can take a long time. Notify trades raw SES unit cost for a thinner setup: API key, domain verify, send.
Related
- PHP guide (same HTTP contract)
- Quick start
- Migrate from SMTP
- Pricing
Comments
Loading comments…