# 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.

Published 2026-11-02. Source: https://mailions.com/blog/send-email-laravel

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](/blog/what-is-spf-record) and [what is DKIM](/blog/what-is-dkim) cover the records, and [the checker post](/blog/check-spf-dkim-dmarc) shows how to confirm they pass.

Then create a mailbox such as `noreply@yourdomain.com` for the SMTP login.

## Configure `.env`

```
MAIL_MAILER=smtp
MAIL_HOST=mail.example.com
MAIL_PORT=587
MAIL_USERNAME=noreply@yourdomain.com
MAIL_PASSWORD=your-mailbox-password
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME="${APP_NAME}"
```

Use your provider's real host name instead of `mail.example.com`. The [mailbox setup guide](/help/mailbox-setup) 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](/blog/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:

```php
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:

```php
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:

```php
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:

```php
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](/temp). 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:

```php
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:

```php
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 <noreply@yourdomain.com>',
        '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](/help/sending-api) 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_ADDRESS` on the same domain as the login
- Plain text and HTML views
- A queue worker running for queued mail
- `log` mailer locally, `Mail::fake()` in tests
- Exceptions caught and logged

To send from your own domain, [contact us](/contact) 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.
