# Send email from Laravel

Install prontuno/prontuno-laravel, send Mailables and notifications through the prontuno mail driver, or call the API with the Prontuno facade.

This guide shows how to send transactional email from a Laravel application with the official `prontuno/prontuno-laravel` package. There are two ways to send, and you can use both in the same application:

- **The `prontuno` mail driver.** Your existing Mailables, notifications, and `Mail::` calls send through Prontuno. You change configuration, not code.
- **The `Prontuno` facade.** Call the Prontuno API directly when you want email IDs back, batches of up to 1,000 emails, or to manage domains and webhooks.

## Before you begin

- [Verify a sending domain](https://prontuno.com/docs/sending/verify-sending-domain/).
- [Create a Sending API key](https://prontuno.com/docs/sending/api-keys/) for that domain.
- Use Laravel 12 or 13 on PHP 8.3 or newer.

## Install the package

```bash
composer require prontuno/prontuno-laravel
```

Laravel discovers the package automatically.

## Add your keys

Add the Sending API key to `.env`:

```dotenv
PRONTUNO_SENDING_KEY=your-sending-key
```

If the application also reads email logs or manages webhooks, add a Management key as `PRONTUNO_MANAGEMENT_KEY`. The package uses the Sending key for sends and the Management key for everything else. An application that needs only one key can set `PRONTUNO_API_KEY` instead.

## Send through the mail driver

Add a `prontuno` mailer to the `mailers` array in `config/mail.php`:

```php
'mailers' => [
    'prontuno' => [
        'transport' => 'prontuno',
    ],

    // ...
],
```

Make it the default mailer, and set a From address on your verified domain:

```dotenv
MAIL_MAILER=prontuno
MAIL_FROM_ADDRESS=hello@example.com
MAIL_FROM_NAME="${APP_NAME}"
```

Your Mailables and notifications now send through Prontuno:

```php
use App\Mail\OrderShipped;
use Illuminate\Support\Facades\Mail;

Mail::to($order->user)->send(new OrderShipped($order));
```

The driver sends the subject, the HTML and text bodies, `cc`, `bcc`, the first reply-to address, custom headers, and attachments, including inline images. Each send uses the message’s `Message-ID` as its idempotency key, so a retried send never delivers twice. If Prontuno rejects a message, the driver throws a Symfony `TransportException`.

After sending, the Prontuno email ID is in the `X-Prontuno-Email-ID` header of the sent message. When there are several recipients, the header holds one ID for each, separated by commas.

### Send from another domain

Each Sending key belongs to one domain. To send from a second domain, add a mailer with its own key:

```php
'prontuno-billing' => [
    'transport' => 'prontuno',
    'key' => env('PRONTUNO_BILLING_SENDING_KEY'),
],
```

Then choose that mailer when you send:

```php
Mail::mailer('prontuno-billing')->to($user)->send(new InvoicePaid($invoice));
```

## Send with the facade

Use the `Prontuno` facade, or inject `Prontuno\Laravel\Prontuno`, to call the API directly:

```php
use Prontuno\Laravel\Facades\Prontuno;

$result = Prontuno::emails()->send([
    'from' => 'Acme <receipts@example.com>',
    'to' => 'ada@example.com',
    'subject' => 'Your receipt',
    'html' => '<p>Thanks for your order.</p>',
    'text' => 'Thanks for your order.',
], idempotencyKey: "order-{$order->id}-receipt");

$emailId = $result['emails'][0]['id'];
```

The response contains one entry for each recipient. Its `status` is `queued` when Prontuno accepted the email, or `rejected` with a `rejection_code` and `rejection_reason`, for example when the address is on your [suppression list](https://prontuno.com/docs/deliverability/suppressions/).

The idempotency key makes the send safe to retry, including when a queued job runs twice. Use a key that identifies the event that triggered the email.

### Attach files

Attachment content is base64 encoded:

```php
use Illuminate\Support\Facades\Storage;
use Prontuno\Laravel\Facades\Prontuno;

Prontuno::emails()->send([
    'from' => 'Acme <billing@example.com>',
    'to' => 'ada@example.com',
    'subject' => 'Your invoice',
    'text' => 'Your invoice is attached.',
    'attachments' => [[
        'filename' => 'invoice.pdf',
        'content' => base64_encode(Storage::get('invoices/1042.pdf')),
        'encoding' => 'base64',
        'content_type' => 'application/pdf',
    ]],
]);
```

An email with its attachments must be 25 MB or less.

### Send a batch

Send up to 1,000 emails in one request:

```php
use Illuminate\Support\Str;
use Prontuno\Laravel\Facades\Prontuno;

Prontuno::emails()->sendBatch(
    $users->map(fn (User $user) => [
        'from' => 'hello@example.com',
        'to' => $user->email,
        'subject' => 'Your account is ready',
        'text' => "Hi {$user->name}, your account is ready.",
    ])->all(),
    idempotencyKey: (string) Str::uuid(),
);
```

Prontuno validates every email before accepting any of them, so an invalid item rejects the whole batch and nothing is sent.

### Handle errors

When the API rejects a request, the package throws an `ApiException`:

```php
use Prontuno\Laravel\Exceptions\ApiException;

try {
    Prontuno::emails()->send($payload, idempotencyKey: $key);
} catch (ApiException $e) {
    report($e);

    if ($e->isRetryable()) {
        $this->release(60); // try the queued job again later
    }
}
```

`ApiException` also exposes `status()`, `errorCode()`, `requestId()`, `errors()` for field and batch validation details, and `suggestedAction()`. Network failures throw `Prontuno\Laravel\Exceptions\ConnectionException`.

## Test your code

The package sends requests through Laravel’s HTTP client, so `Http::fake()` stops them in tests:

```php
use Illuminate\Support\Facades\Http;

Http::fake([
    'api.prontuno.net/v1/emails' => Http::response([
        'emails' => [[
            'id' => 'email_test',
            'to' => 'ada@example.com',
            'status' => 'queued',
        ]],
    ], 202),
]);
```

Code that sends through the mail driver can use `Mail::fake()` as usual.

## Check delivery

A `queued` status means Prontuno accepted the email, not that it was delivered. To follow what happens next:

- Look the email up with `Prontuno::emails()->retrieve($id)`, which needs a Management key.
- Receive delivery, bounce, and complaint events as they happen with [webhooks](https://prontuno.com/docs/integrations/verify-webhooks-with-an-sdk/).
- Open **Sent emails** in the dashboard. See [Understand email statuses and logs](https://prontuno.com/docs/deliverability/understand-email-statuses-and-logs/).

## Next steps

[Verify webhooks with an SDKVerify Prontuno webhook signatures and handle delivery, bounce, and complaint events with the official Node.js, PHP, and Laravel SDKs.](https://prontuno.com/docs/integrations/verify-webhooks-with-an-sdk/)

[SDK referenceConfiguration options, methods for every resource, API key routing, errors, and webhook helpers in the Prontuno Node.js, PHP, and Laravel SDKs.](https://prontuno.com/docs/integrations/sdk-reference/)

[The Laravel package on GitHubSource code, issues, and releases on GitHub.](https://github.com/prontuno/prontuno-laravel)
