# Send email with the PHP SDK

Install prontuno/prontuno-php with Composer, configure your keys, and send email, attachments, and batches from any PHP application.

This guide shows how to send transactional email from a PHP application with the official `prontuno/prontuno-php` package. It works in any PHP application, with or without a framework. For a Laravel application, use [the Laravel package](https://prontuno.com/docs/integrations/send-email-from-laravel/) instead.

## 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 PHP 8.3 or newer, with Composer.

## Install the SDK

```bash
composer require prontuno/prontuno-php
```

## Configure the client

Store the Sending API key in an environment variable, such as `PRONTUNO_SENDING_KEY`, and never in source control. Then create a client:

```php
use Prontuno\Prontuno;

$prontuno = new Prontuno(
    sendingKey: getenv('PRONTUNO_SENDING_KEY') ?: null,
);
```

`getenv()` returns `false` when a variable isn’t set, so `?: null` turns that into “no key”, and the SDK reports a clear configuration error instead of sending an empty key.

If the same application also reads email logs or manages webhooks, add a Management key as `managementKey`. The client uses the Sending key for sends and the Management key for everything else.

## Send an email

```php
$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-1042-receipt');

foreach ($result['emails'] as $email) {
    echo $email['id'], ' ', $email['status'], PHP_EOL;
}
```

The `from` address must be on the domain the Sending key belongs to. Every email needs an `html` body, a `text` body, or both. Requests and responses are plain PHP arrays that use the API’s field names.

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/). Store the `id` to look the email up later.

The idempotency key makes the send safe to retry. If the request times out, the SDK retries it with the same key, and Prontuno sends the email only once. Use a key that identifies the event that triggered the email.

## Send to several recipients

`to`, `cc`, and `bcc` each accept one address or an array of addresses, up to 100 recipients in total:

```php
$prontuno->emails->send([
    'from' => 'Acme <reports@example.com>',
    'to' => ['ada@example.com', 'grace@example.com'],
    'cc' => 'team@example.com',
    'reply_to' => 'support@example.com',
    'subject' => 'Weekly report',
    'text' => "This week's report is ready.",
]);
```

## Attach files

The `Attachment` helper reads and encodes files for you:

```php
use Prontuno\Attachment;

$prontuno->emails->send([
    'from' => 'Acme <billing@example.com>',
    'to' => 'ada@example.com',
    'subject' => 'Your invoice',
    'text' => 'Your invoice is attached.',
    'attachments' => [
        Attachment::fromPath('/path/to/invoice.pdf', contentType: 'application/pdf'),
        Attachment::fromContent('notes.txt', 'Plain text content', 'text/plain'),
    ],
]);
```

To show an image inside the HTML body, pass a `contentId` and reference it as `cid:` in an `img` tag. An email with its attachments must be 25 MB or less.

## Send a batch

Send up to 1,000 emails in one request:

```php
$batch = $prontuno->emails->sendBatch([
    [
        'from' => 'hello@example.com',
        'to' => 'ada@example.com',
        'subject' => 'Hi Ada',
        'text' => 'Hi Ada',
    ],
    [
        'from' => 'hello@example.com',
        'to' => 'grace@example.com',
        'subject' => 'Hi Grace',
        'text' => 'Hi Grace',
    ],
], idempotencyKey: 'welcome-batch-2026-10-24');
```

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

## Send from another domain

Each Sending key belongs to one domain. To send from a second domain, create a Sending key for it, then get a client that uses it:

```php
$billing = $prontuno->withSendingKey((string) getenv('PRONTUNO_BILLING_SENDING_KEY'));

$billing->emails->send([
    'from' => 'Acme Billing <invoices@billing.example.com>',
    'to' => 'ada@example.com',
    'subject' => 'Your invoice',
    'text' => 'Your invoice is attached.',
]);
```

The new client keeps every other setting, including the Management key.

## Handle errors

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

```php
use Prontuno\Exceptions\ApiException;

try {
    $prontuno->emails->send($email, idempotencyKey: $idempotencyKey);
} catch (ApiException $e) {
    error_log(sprintf(
        'Prontuno %d %s: %s (request %s)',
        $e->status(),
        $e->errorCode(),
        $e->getMessage(),
        $e->requestId(), // include this when contacting support
    ));

    if ($e->isRetryable()) {
        // Safe to try again later with the same idempotency key.
    }

    throw $e;
}
```

`$e->errors()` lists the fields that failed validation, and for a batch, which item they belong to. `$e->suggestedAction()` names the next step, such as `fix_input` or `top_up_credits`. Network failures throw `ConnectionException`. Every SDK exception implements `Prontuno\Exceptions\ProntunoException`, so you can catch them all at once.

## Check delivery

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

- Look the email up by its ID 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/).

To test bounces and deferrals without emailing real people, send to the addresses in [Mailbox Simulator](https://prontuno.com/docs/deliverability/test-with-mailbox-simulator/).

## 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 PHP SDK on GitHubSource code, issues, and releases on GitHub.](https://github.com/prontuno/prontuno-php)
