Send email with Laravel through Volanea’s REST API when your application needs a direct, server-to-server transactional delivery path for receipts, password resets, account alerts, invitations, or verification messages. This guide uses Laravel, PHP, and Guzzle to make one authenticated JSON request to Volanea’s POST /v1/send endpoint.

The integration deliberately uses the REST API rather than a fictional Laravel-specific package. Laravel applications can call a standard HTTPS endpoint cleanly, keep the Volanea secret key outside source control, handle provider errors in one place, and later move the same send logic into a queued job. The result is a small integration that is easy to test locally and suitable as the foundation for production mail sending.

What this Laravel integration sends

This guide sends one transactional email with these message fields:

  • a verified sender address in from
  • one recipient address in to
  • a subject
  • an HTML body in html
  • a plain-text fallback in text

The request is sent as JSON to Volanea’s REST API. The API key is read from the VOLANEA_API_KEY environment variable through Laravel configuration, not hard-coded in the controller. The example also includes an Idempotency-Key header so a retry of the same logical operation can be identified safely by the API.

This is a transactional send pattern: application code decides that a specific user should receive a specific operational message. It is not a bulk campaign loop. A welcome email, receipt, password-reset notification, and security alert are all typical uses because they are tied to a user action or application state.

Before sending, make sure the address used in VOLANEA_FROM_EMAIL belongs to a sending domain you have authenticated in Volanea. A successful API request only means the provider accepted the request for processing; inbox placement still depends on a valid sender identity, recipient mailbox policies, content, and the receiving mail server.

Prerequisites for sending email with Laravel

You need an existing Laravel application running on PHP, plus a Volanea secret API key. Volanea’s API reference identifies the send endpoint as POST /v1/send on https://api.volanea.com, and secret keys use sk_… or sk_test_… formats.

Have these values ready before copying the code:

  1. A Volanea API key. Store it only in your local environment file, deployment secret manager, or CI/CD secret store.
  2. A verified sender email address. Use an address at a domain you have configured for sending, such as notifications@example.com.
  3. A recipient address you control. Use it for the first end-to-end test so you can inspect the received message without involving a customer.
  4. A current Laravel application. The example uses Laravel’s normal configuration system and a standard PHP command class.
  5. Composer. Composer installs Guzzle, the HTTP client used by the sample.

Do not put a Volanea secret key in browser JavaScript, a public mobile application, a committed .env file, or a route rendered to untrusted users. REST email requests must originate from trusted server-side code. If someone obtains a sending credential, they may be able to make requests using your account, consume sending capacity, or damage your domain’s sending reputation.

For the API endpoint’s full request and response contract, consult the email API reference and setup guides as you implement additional message features. This guide intentionally stays focused on a single direct transactional send.

Install the PHP HTTP dependency

Laravel includes useful HTTP tooling in many application installations, but this guide explicitly installs Guzzle so the dependency and the code path are unambiguous.

Run this command from the root directory of the Laravel project:

composer require guzzlehttp/guzzle

Composer adds Guzzle to your project dependencies and makes the GuzzleHttp\Client class available through Composer’s autoloader. No Volanea-specific Laravel SDK is required for this integration because the API is a standard authenticated JSON-over-HTTPS endpoint.

After installation, verify that Composer completed successfully:

composer show guzzlehttp/guzzle

If Composer cannot update the lock file, check that you are in the Laravel project directory and that the PHP version required by the resolved Guzzle release is compatible with the PHP runtime used by the application. In a containerized deployment, install the dependency during the image build rather than at runtime.

Configure the Volanea API key and sender identity

Add the following values to .env. Replace the placeholder values with a real Volanea secret key and a sender address from your authenticated domain.

VOLANEA_API_KEY=sk_test_replace_with_your_secret_key
VOLANEA_FROM_EMAIL="Volanea Demo <notifications@example.com>"

Keep the quote marks around the sender value when it includes a display name and angle brackets. They ensure the full sender value is parsed as one environment variable value.

Next, add a volanea entry to the array returned by config/services.php:

// config/services.php

return [
    // Existing service configuration...

    'volanea' => [
        'key' => env('VOLANEA_API_KEY'),
        'from' => env('VOLANEA_FROM_EMAIL'),
    ],
];

This configuration pattern matters in production. The command below reads config('services.volanea.key'), while config/services.php is the only place that reads the environment variable. That keeps configuration compatible with Laravel’s configuration cache. Avoid calling env() throughout application classes because values may not be available as expected once configuration has been cached.

If you have already run php artisan config:cache, rebuild the cache after adding or changing environment-backed configuration:

php artisan config:clear
php artisan config:cache

For local development, php artisan config:clear is normally enough. In managed hosting, add VOLANEA_API_KEY and VOLANEA_FROM_EMAIL to the platform’s environment or secrets configuration, then deploy or restart the application according to that platform’s release process.

Complete Laravel code sample

Create the following Artisan command at app/Console/Commands/SendVolaneaTestEmail.php. It is complete: it validates its inputs, initializes Guzzle with the Volanea API key from Laravel configuration, sends one message, handles non-success responses, and prints the returned JSON when possible.

<?php

namespace App\Console\Commands;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Console\Command;
use Illuminate\Support\Str;

class SendVolaneaTestEmail extends Command
{
    /**
     * Example:
     * php artisan volanea:send-test you@example.com
     */
    protected $signature = 'volanea:send-test {to : Recipient email address}';

    protected $description = 'Send one transactional test email through Volanea';

    public function handle(): int
    {
        $apiKey = config('services.volanea.key');
        $from = config('services.volanea.from');
        $to = (string) $this->argument('to');

        if (! is_string($apiKey) || $apiKey === '') {
            $this->error('VOLANEA_API_KEY is missing. Add it to .env or your deployment secrets.');

            return self::FAILURE;
        }

        if (! is_string($from) || $from === '') {
            $this->error('VOLANEA_FROM_EMAIL is missing. Add a verified sender address.');

            return self::FAILURE;
        }

        if (! filter_var($to, FILTER_VALIDATE_EMAIL)) {
            $this->error('The recipient must be a valid email address.');

            return self::FAILURE;
        }

        $client = new Client([
            'base_uri' => 'https://api.volanea.com',
            'timeout' => 15,
            'connect_timeout' => 5,
        ]);

        try {
            $response = $client->request('POST', '/v1/send', [
                'headers' => [
                    'Authorization' => 'Bearer '.$apiKey,
                    'Accept' => 'application/json',
                    'Idempotency-Key' => (string) Str::uuid(),
                ],
                'json' => [
                    'from' => $from,
                    'to' => $to,
                    'subject' => 'Your Volanea + Laravel test email',
                    'html' => '<h1>It works</h1><p>This transactional email was sent from Laravel through Volanea.</p>',
                    'text' => 'It works. This transactional email was sent from Laravel through Volanea.',
                ],
            ]);
        } catch (GuzzleException $exception) {
            $this->error('Volanea request failed: '.$exception->getMessage());

            return self::FAILURE;
        }

        $status = $response->getStatusCode();
        $body = (string) $response->getBody();
        $decoded = json_decode($body, true);

        $this->info("Volanea accepted the request with HTTP {$status}.");

        if (json_last_error() === JSON_ERROR_NONE) {
            $this->line(json_encode($decoded, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
        } elseif ($body !== '') {
            $this->line($body);
        }

        return self::SUCCESS;
    }
}

Laravel applications conventionally discover command classes in app/Console/Commands. If your application uses an older or customized console bootstrap that does not discover this directory, register the command in that application’s console kernel or command registration configuration before running it.

Now run the command with an email address you control:

php artisan volanea:send-test you@example.com

The command creates a new Guzzle client, which is the initialization step for the REST client in this guide. Its base_uri is Volanea’s API base URL. The request then adds the bearer credential from VOLANEA_API_KEY, tells the server that the caller accepts JSON responses, and provides a JSON message payload.

The json option is important. Guzzle serializes the PHP array to JSON and sends the correct JSON request content type. Do not manually call json_encode() and pass the result to the json option; that produces a JSON string containing JSON rather than the object the API expects.

Understand the request before adapting it

The request has three parts that should remain separate in your application design: authentication headers, delivery-control headers, and the message payload.

Authentication header

The API key is sent through this header:

Authorization: Bearer sk_...

The API key authenticates the server-side request. It is not a recipient-specific token, and it should not be embedded in a reset link, an email template, or application logs. Redact it if you log outgoing requests or exceptions.

JSON message payload

The payload provides the message content and addressing information:

{
  "from": "Volanea Demo <notifications@example.com>",
  "to": "you@example.com",
  "subject": "Your Volanea + Laravel test email",
  "html": "<h1>It works</h1><p>This transactional email was sent from Laravel through Volanea.</p>",
  "text": "It works. This transactional email was sent from Laravel through Volanea."
}

Include both html and text whenever possible. HTML gives you layout, links, and branding. Plain text provides a readable fallback for mail clients that do not render HTML, recipients who prefer text-only messages, security tools, and accessibility-oriented workflows. The two versions should communicate the same essential information.

The from value must be aligned with a sender identity configured for your account. Do not substitute arbitrary customer email addresses into from. If you need a user’s address to receive replies, use a deliberately configured reply strategy and test it, rather than presenting an unauthenticated address as the sender.

Idempotency key

The sample creates a UUID for the Idempotency-Key header:

'Idempotency-Key' => (string) Str::uuid(),

An idempotency key helps an API recognize safe retries of the same logical request. In the test command, each invocation intentionally creates a new message and therefore creates a new UUID. In a real order-confirmation or password-reset flow, generate one durable key per business event and persist it with the event or outbound-email record. Reusing that exact key for a retry is what prevents a timeout from turning into duplicate customer email.

For example, an order receipt might use a value derived from an immutable order event identifier. If the application loses the HTTP response after the provider accepted the message, retry with the same idempotency key rather than generating a new one.

Test the integration safely

Start by sending only to an inbox you own. Confirm not only that the command prints a successful HTTP status, but also that the message arrives, has the expected sender, renders correctly, and includes the text fallback.

A practical first-test checklist is:

  • Verify that VOLANEA_API_KEY is present in the runtime environment.
  • Verify that VOLANEA_FROM_EMAIL uses a sender domain configured for Volanea.
  • Send to a controlled recipient address.
  • Inspect the visible sender, subject, HTML formatting, and plain-text alternative.
  • Check spam or junk folders before assuming the API failed.
  • Read the response body printed by the command and retain only non-sensitive diagnostic details.

During local development, do not point a route or controller directly at a public URL merely to test mail. The Artisan command keeps the test explicit and prevents an unauthenticated visitor from causing sends. Once the basic connection is confirmed, move the sending operation behind the authenticated business workflow that requires it: account signup, receipt creation, invitation acceptance, password-reset initiation, or security notification.

Test actual variables too. A static test only proves network access and basic authorization. A production email often includes a customer name, a link, a price, a date, or a one-time code. Render representative data in a staging environment and verify that user-controlled content is escaped before it enters HTML.

Move the send into application code

The command is a safe first integration, not necessarily the final shape of your production code. In an application, extract the Guzzle request into a dedicated service class, then call that service from a controller, listener, action, or queued job.

Keep the responsibility boundaries clear:

  • Controller or action: validates the user request and initiates the business operation.
  • Domain or application service: decides whether an email should be sent and records the business event.
  • Email delivery service: builds the Volanea request and parses provider errors.
  • Queue job: performs network I/O outside the web request when latency and retry control matter.
  • Webhook handler: records later delivery events when the provider reports them.

Do not make successful email acceptance the only proof that a database transaction should commit. For example, create an order or account record first, then use an after-commit event or queued job to perform the send. That avoids sending a receipt for a transaction that is later rolled back.

Likewise, do not automatically retry every HTTP failure without considering whether Volanea may already have accepted the request. Use an idempotency key, preserve the logical event identifier, and apply bounded retry behavior. Retryable network failures and rate limits are different from malformed request payloads or an unauthorized API key. The latter require configuration or code changes, not repeated attempts.

Production considerations for transactional email

A direct API call is only one part of a reliable transactional email system. Production readiness comes from the operational choices around the call.

Keep secrets outside the repository

Use .env only for local development. In production, use the secret store provided by your hosting platform, container orchestrator, or CI/CD environment. Limit access to people and processes that actually need the credential. Rotate the key if it is exposed, and replace it in every deployed environment before revoking the old key.

Never log the full request headers. If you use HTTP request logging, configure redaction for Authorization. Also avoid logging raw email bodies if those bodies may include personal information, reset URLs, invoices, or account-specific data.

Use queues for user-facing requests

A synchronous HTTP call adds provider and network latency to the web request. For low-volume internal tooling, that may be acceptable. For signup flows, billing events, or notifications serving many users, use Laravel queues so the application can respond to the user promptly while a worker handles the delivery request.

Queueing is not a license to retry blindly. Configure the job to carry a stable idempotency key, use a limited number of attempts, and capture enough error information to diagnose failures. A failed job should be visible to your team rather than silently discarded.

Treat delivery as an event stream

An accepted send request is the beginning of the delivery lifecycle, not the end. A recipient can later bounce, complain, unsubscribe where applicable, or receive the message successfully. Your application should decide what it needs to do with those facts: mark an address as undeliverable, stop attempting a notification type, notify support, or update a customer record.

Avoid using email open tracking as the source of truth for security or billing flows. Opens can be blocked, pre-fetched, or generated by privacy tools. Use your own authenticated application events to determine whether a person completed an important action.

Design message content for the mailbox

Use a direct, descriptive subject. Make the first lines of the plain-text and HTML content useful even if images do not load. Include a clear reason the recipient is receiving the message. For sensitive actions, avoid including secrets in the subject line because subjects can appear in lock-screen notifications and mail previews.

For transactional messages with links, use absolute HTTPS URLs, and make expiration and account context clear. For example, a password-reset email should say which product sent it, what action the link performs, and what to do if the recipient did not request it.

Common errors when sending with Laravel

401 or 403 authentication failures

Authentication failures usually mean the API key is missing, invalid, revoked, copied with an extra character, or not available to the running Laravel process. Confirm that VOLANEA_API_KEY exists in the environment used by the command, worker, container, or web process—not merely in a local .env file on your laptop.

If the value was changed after php artisan config:cache, rebuild configuration cache and restart long-running workers. Also verify that the request uses the bearer authentication header exactly as shown in the example. Do not send the key as a query parameter or expose it in frontend code.

400, 422, or another validation error

A validation response means the API received the request but rejected one or more fields. Read the response body printed by the command; it commonly provides the useful clue. Check that from, to, subject, and at least the content fields required by your intended request are present and are strings rather than nested PHP arrays.

Validate recipient addresses before sending, as the command does. Validate application-specific inputs too: a blank customer name, missing reset URL, or malformed HTML may not always stop an API request, but it can still produce an unusable email.

Wrong Content-Type or malformed JSON

The Volanea send endpoint expects a JSON request. In Guzzle, use the json option exactly as in the sample. It serializes the payload and applies JSON content handling for you.

A common mistake is using body with a PHP array, using form_params, or manually encoding JSON twice. Another mistake is declaring Content-Type: application/json while sending form data. If you need to inspect the outgoing shape during development, log a sanitized copy of the payload only—never the authorization header.

The sender domain or from address is not accepted

The sender must correspond to a configured and authenticated sending identity. A valid-looking string such as Support <support@random-domain.example> is not automatically authorized just because the syntax is valid.

Check the sender domain setup in Volanea, confirm DNS authentication records are published as instructed for that domain, and ensure the configured VOLANEA_FROM_EMAIL matches the identity you intend to use. Do not solve this by changing the sender to a customer’s personal email address.

The command cannot find the class or command

If php artisan volanea:send-test is not listed, verify that the PHP file is saved at app/Console/Commands/SendVolaneaTestEmail.php, the namespace is App\Console\Commands, and the class name matches the filename. Run Composer autoload optimization if needed:

composer dump-autoload

Customized Laravel applications may use explicit command registration. In that case, register the command using the project’s existing console configuration pattern.

Timeout, DNS, TLS, or connection errors

A connection failure happens before Volanea can reliably process the request. Check outbound network access from the actual runtime environment, not just from your development machine. Containers, private networks, firewalls, and restrictive hosting plans can prevent outbound HTTPS connections.

The sample uses a 5-second connect timeout and a 15-second overall timeout. Those are reasonable starting points, but tune them based on your application’s latency budget. If you retry after an uncertain timeout, reuse the same idempotency key for the same business event.

Async and await mistakes

Laravel and the PHP code in this guide do not use JavaScript’s async and await syntax. Do not copy a JavaScript API example containing await fetch(...) into a PHP controller or command; it will be a PHP syntax error.

Guzzle’s request() method is synchronous. If you intentionally use Guzzle promises through requestAsync(), you must explicitly wait for or otherwise manage the promise and handle rejections. For most Laravel transactional-email flows, a queued job with normal synchronous request() code is simpler, easier to observe, and easier to retry safely.

A 2xx response but no email in the inbox

First, check the recipient’s spam and junk folders. Then verify that the recipient address is correct, the sender domain is authenticated, and the returned response has been recorded. An accepted request can still be subject to downstream mailbox filtering or later delivery events.

Do not resend repeatedly from a button while investigating. Repeated sends can create confusing duplicates and worsen a bad customer experience. Use your provider-side event information and application logs to distinguish accepted, deferred, bounced, and delivered outcomes where available.

Next steps: webhooks and templates

After the first send succeeds, add webhooks to receive delivery lifecycle events in your application. A webhook endpoint is an HTTPS route your application controls; it receives provider event notifications so you can react to bounces, complaints, deliveries, and other message-state changes. Verify webhook signatures according to the API documentation, reject invalid requests, and make event processing idempotent because providers may retry delivery of webhook notifications.

Next, move repeated markup into reusable templates. Templates separate application data from presentation: your Laravel code can send a template identifier and the variables needed to render it instead of rebuilding the entire HTML body in every action. This reduces duplication across welcome messages, receipts, and account alerts, while making content review easier for the people responsible for email copy and design.

When adopting templates, establish a versioning and test process. Preview messages with representative data, keep required variables documented, and decide whether a template update should affect queued messages that have not yet been sent. For sensitive transactional email, make sure templates do not accidentally expose data through previews, logs, or shared test inboxes.

FAQ

Do I need a Volanea Laravel SDK?

No. This guide uses Guzzle to call Volanea’s REST endpoint directly, so there is no dependency on a provider-specific Laravel SDK. The explicit install command is composer require guzzlehttp/guzzle.

Where should I store the Volanea API key?

Store it in VOLANEA_API_KEY as an environment secret. Map that variable through config/services.php, then access it with config('services.volanea.key') in application code. Do not commit the key or expose it to browsers.

Should I send email synchronously from a Laravel controller?

For a simple internal action, it can be acceptable. For customer-facing production workflows, use a Laravel queue job so network latency and temporary provider failures do not delay the HTTP response. Preserve an idempotency key for each logical email event.

Why include both HTML and text email content?

HTML provides a richer presentation, while text provides a robust fallback for mail clients and recipients that do not render HTML. Keeping both versions aligned improves readability and makes important transactional information available in more contexts.

Can I use this approach for password-reset and receipt emails?

Yes. Those are transactional email use cases. Generate secure, purpose-limited links or codes in your application, keep sensitive details out of subjects, use a verified sender identity, and send from trusted server-side Laravel code.