# Send email with Laravel

> Send email from Laravel with Email Fast: a small injectable service on the built-in HTTP client, notification-style helpers, config/env keys, idempotency.

Canonical: https://emailfast.dev/send-email-with/laravel

In Laravel, wrap the API in one small injectable service — the same shape you'd give
a custom notification channel — and read the key through `config()`, not `env()`.
Laravel's built-in HTTP client does the POST, so there is nothing new to install.

```php
<?php
// app/Services/EmailFast.php
namespace App\Services;

use Illuminate\Support\Facades\Http;

class EmailFast
{
    public function send(
        string $to,
        string $subject,
        string $html,
        array $data = [],
        ?string $idempotencyKey = null,
    ): array {
        return Http::withToken(config('services.emailfast.key'))
            ->timeout(10)
            ->post('https://api.emailfast.dev/v1/emails', [
                'to' => $to,
                'subject' => $subject,
                'html' => $html,
                'data' => $data,
                'idempotency_key' => $idempotencyKey,
            ])
            ->throw()
            ->json();
    }
}
```

```php
// config/services.php
'emailfast' => ['key' => env('EMAILFAST_API_KEY')], // ef_sandbox_... while testing
```

```php
// anywhere — a controller, a queued job, a listener
app(\App\Services\EmailFast::class)->send(
    to: $user->email,
    subject: 'Confirm your account',
    html: '<p>Hi {{name}}, confirm your address here.</p>',
    data: ['name' => $user->name],
    idempotencyKey: "confirm-{$user->id}", // a queue retry replays, never re-sends
);
```

a 202 from the API means the send is committed to a durable, partitioned outbox before we answer — a crash can't lose it, and a retry with the same idempotency key can't double-send.

## Gotchas

- `env()` returns `null` once you run `php artisan config:cache` — only `config/services.php` may call it. Everywhere else, `config('services.emailfast.key')`.
- Send from a queued job or a `ShouldQueue` listener so email never adds latency to a web request; the idempotency key is what makes queue retries safe.
- Using Laravel notifications? Lift this service into a custom channel (`via()` returning your channel class) and keep it as the single door to the API.
- `->throw()` puts the API's error body in the exception message — log it; it names the exact rejection reason (invalid address, suppressed recipient, quota).

## Next

- [PHP SDK quickstart](/docs/quickstart-php) — the typed client
- [The email API](/features/email-api) — batch, scheduling, message timelines
- [Plain PHP recipe](/send-email-with/php)
- [All docs](/docs)
