How to send email in Laravel with SMTP or an HTTP API
Configure Laravel to send transactional email from your own domain over SMTP, queue messages with Mailables, and test locally without sending real mail.
Laravel has a clean mail system built on Symfony Mailer. It speaks SMTP out of the box, so any mail service that offers an SMTP login works with a handful of lines in .env. The setup is short. The parts that bite later are queues, local testing and failure handling.
Authenticate your domain first
Laravel will happily send a message that every provider then junks. Before you write code, publish SPF, DKIM and DMARC for the domain you're sending from, and check them. What is an SPF record and what is DKIM cover the records, and the checker post shows how to confirm they pass.
Then create a mailbox such as [email protected] for the SMTP login.
Configure .env
MAIL_MAILER=smtp
MAIL_HOST=mail.example.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=your-mailbox-password
[email protected]
MAIL_FROM_NAME="${APP_NAME}"
Use your provider's real host name instead of mail.example.com. The mailbox setup guide lists the one for Mailions.
On Laravel 10 and older you also set MAIL_ENCRYPTION=tls. On Laravel 11 and newer that variable is gone, and Symfony Mailer upgrades to STARTTLS on its own for port 587. If you need to force a particular behaviour, set MAIL_SCHEME (smtp or smtps). SMTP ports explained tells you what these mean.
Make sure MAIL_FROM_ADDRESS is on the same domain as the login. A mismatch is a common reason DMARC fails.
Send your first message
Generate a Mailable:
php artisan make:mail WelcomeMail --markdown=emails.welcome
Then send it:
use App\Mail\WelcomeMail;
use Illuminate\Support\Facades\Mail;
Mail::to($user->email)->send(new WelcomeMail($user));
Inside the Mailable, set the subject in envelope() and the view in content(). Provide a plain text view as well as the HTML one:
public function content(): Content
{
return new Content(
markdown: 'emails.welcome',
text: 'emails.welcome-text',
);
}
Having both parts helps with filtering, and with people who don't load images.
Queue it
Mail::send() blocks the request until the SMTP conversation ends. If the mail server is slow, your user waits. Queue the message instead:
Mail::to($user->email)->queue(new WelcomeMail($user));
Or make the Mailable implement ShouldQueue. You need a queue driver (database or Redis) and a worker running with php artisan queue:work. If the worker isn't running, nothing is sent and nothing complains, which is the most common "Laravel email isn't working" cause after bad credentials.
Do not queue a login code the same way and forget about it. A code that arrives after it expires is useless. Send those immediately or with a short timeout.
Test without sending
For local development, set MAIL_MAILER=log. Messages are written to storage/logs/laravel.log instead of leaving the machine. In tests, use Mail::fake() and assert on what was sent:
Mail::fake();
// ...run the code...
Mail::assertSent(WelcomeMail::class, fn ($mail) => $mail->hasTo($user->email));
When you do want to see a real message arrive, send it to a public temporary inbox. Choose any name and watch it appear. Free inboxes delete mail after 30 minutes and anyone who knows the address can read it, so use dummy content only.
Handle failures
SMTP errors throw exceptions, such as TransportException. Catch them where the message matters, and log enough to debug:
try {
Mail::to($user->email)->send(new ResetMail($user));
} catch (\Throwable $e) {
report($e);
// show a friendly "try again" message
}
Queued jobs retry automatically up to the tries count you set on the job. Decide per message whether retrying is wise. A receipt, yes. A code, rarely.
Or call an HTTP API instead
If your host blocks outgoing SMTP, use an API. Laravel's HTTP client makes it short:
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.mail.key'))
->withHeaders(['Idempotency-Key' => "welcome/{$user->id}"])
->timeout(15)
->post(config('services.mail.url') . '/api/v1/emails', [
'from' => 'Your App <[email protected]>',
'to' => $user->email,
'subject' => 'Welcome aboard',
'text' => 'Thanks for signing up.',
])
->throw();
The idempotency key means a retry after a timeout can't send the message twice. The API guide lists every field and error.
Problems you will probably meet
"Connection could not be established." The host name is wrong, or your hosting company blocks outgoing SMTP. Test from the server itself with the openssl s_client -starttls smtp -connect host:587 command. If it times out, switch to the HTTP route above.
"Failed to authenticate on SMTP server." The username must be the full email address, and the password is the mailbox password. After editing .env, clear the cached configuration with php artisan config:clear, since Laravel reads the cached copy in production.
The message sends but lands in spam. Open the headers of a received copy. If SPF, DKIM or DMARC fail, the problem is DNS, not Laravel.
Two copies arrive. A queued job was retried after a timeout, or the Mailable is both queued and sent directly. Search your code for double calls, and give HTTP sends an idempotency key.
Checklist
- Domain authenticated with SPF, DKIM and DMARC
MAIL_FROM_ADDRESSon the same domain as the login- Plain text and HTML views
- A queue worker running for queued mail
logmailer locally,Mail::fake()in tests- Exceptions caught and logged
To send from your own domain, contact us and we'll set up a trial.
Frequently asked questions
How do I send email in Laravel?
Set the mailer, host, port, username and password in .env, create a Mailable class, and send it with Mail::to($address)->send(...). In production, queue the Mailable so the request does not wait for the mail server.
Which SMTP port should Laravel use?
Port 587 with STARTTLS is the usual choice, or 465 with implicit TLS if your provider asks for it. Avoid port 25, which many hosting providers block for outgoing connections.
How can I test Laravel emails without sending them?
Use Mail::fake() in tests to assert that a Mailable was sent, set the log mailer locally to write messages to the log file, and use test inboxes in staging to check the real email end to end.