Skip to content

PHP SDK

Terminal window
composer require vaartix/vaartix-php

The current version is 0.3.0. Works with PHP 8.1 and later, with the standard curl and json extensions and no other dependencies. The package is on Packagist and the source is on GitHub at vaartix-mail/vaartix-php. Step-by-step guides: Laravel and Symfony.

Using an AI assistant such as Claude or Cursor? The MCP server lets it send email and look up delivery status through the same API, without writing code.

use Vaartix\Vaartix;
$vaartix = new Vaartix(); // reads the VAARTIX_API_KEY environment variable
$email = $vaartix->emails->send([
'from' => 'Acme <[email protected]>',
'to' => ['[email protected]'],
'subject' => 'Hello from Vaartix',
'html' => '<strong>It works!</strong>',
]);
echo $email['id']; // em_...

Keys can be snake_case (reply_to, scheduled_at) or the API’s camelCase (replyTo, scheduledAt). Responses are the API’s JSON as arrays, exactly as in the API reference.

// Retry safely: the same key within 24 hours returns the first email instead of sending twice.
$vaartix->emails->send($email, idempotencyKey: "welcome-{$user->id}");
// Send later (up to 30 days ahead), and cancel if plans change.
$scheduled = $vaartix->emails->send([...$email, 'scheduled_at' => '2026-11-01T09:00:00+05:30']);
$vaartix->emails->cancel($scheduled['id']);
// Up to 100 emails in one request.
$vaartix->batch->send([$email1, $email2]);
// Look up an email and its delivery events, or list recent bounces.
$vaartix->emails->get('em_...');
$vaartix->emails->list(['status' => 'bounced', 'days' => 7]);

No domain yet? Send from any address at test.vaartix.com to delivered@, bounced@ or [email protected] to simulate what happens:

$vaartix->emails->send([
'from' => '[email protected]',
'to' => '[email protected]',
'subject' => 'Hello',
'text' => 'It works!',
]);

API and network errors throw Vaartix\Exceptions\VaartixException:

use Vaartix\Exceptions\VaartixException;
try {
$vaartix->emails->send($email);
} catch (VaartixException $e) {
echo $e->type, ' ', $e->statusCode, ' ', $e->getMessage(); // e.g. rate_limited 429 Too many requests...
}

$e->type is a stable code; the full list is in Errors. Network failures have type === 'network_error' and statusCode === null.

Since vaartix/vaartix-php 0.2.0, the package registers a vaartix mail driver automatically (Laravel 12 and 13), so Laravel’s Mail facade, Mailables, notifications and queued mail all go through Vaartix.

.env
MAIL_MAILER=vaartix
MAIL_FROM_ADDRESS=[email protected] # on a domain verified in Vaartix
VAARTIX_API_KEY=vx_live_...
VAARTIX_WEBHOOK_SECRET=whsec_...
config/services.php
'vaartix' => [
'key' => env('VAARTIX_API_KEY'),
'webhook_secret' => env('VAARTIX_WEBHOOK_SECRET'),
],
config/mail.php, inside 'mailers'
'vaartix' => [
'transport' => 'vaartix',
],

That’s all. Existing code keeps working:

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

Laravel’s ->tag('welcome') and ->metadata('order_id', 42) become Vaartix tags, so you can filter by them in the dashboard. SentMessage::getMessageId() returns the Vaartix email ID.

To call the API directly, inject the client; it’s registered as a singleton from services.vaartix.key:

public function store(Request $request, \Vaartix\Vaartix $vaartix)
{
$vaartix->emails->send([
'from' => 'Acme <[email protected]>',
'to' => $request->user()->email,
'subject' => 'Your order is confirmed',
'html' => view('emails.order-confirmed', ['order' => $order])->render(),
], idempotencyKey: "order-confirmed-{$order->id}");
}

Since vaartix/vaartix-php 0.3.0, Symfony Mailer can use Vaartix through a MAILER_DSN. Set the DSN and register the transport factory once:

.env.local
MAILER_DSN=vaartix+api://vx_your_api_key@default
config/services.yaml
services:
Vaartix\Symfony\VaartixTransportFactory:
parent: mailer.transport_factory.abstract
tags: ['mailer.transport_factory']

default means https://api.vaartix.com. Add ?timeout=10 to change the request timeout (seconds). If your key contains + or /, URL-encode them in the DSN. Step by step: Symfony guide.

Check that a webhook request really came from Vaartix before trusting it. Webhook::verify() checks the signature and timestamp (Standard Webhooks) and returns the parsed event. Pass the raw body, not parsed JSON.

use Vaartix\Webhook;
use Vaartix\Exceptions\WebhookVerificationException;
try {
$event = Webhook::verify(file_get_contents('php://input'), getallheaders(), getenv('VAARTIX_WEBHOOK_SECRET'));
} catch (WebhookVerificationException $e) {
http_response_code(400);
exit;
}

Exclude this route from CSRF protection; Vaartix signs the request instead.

use Illuminate\Http\Request;
use Vaartix\Webhook;
use Vaartix\Exceptions\WebhookVerificationException;
Route::post('/webhooks/vaartix', function (Request $request) {
try {
$event = Webhook::verify($request->getContent(), $request->headers->all(), config('services.vaartix.webhook_secret'));
} catch (WebhookVerificationException $e) {
abort(400);
}
if ($event['type'] === 'email.bounced') {
// stop emailing $event['data']['recipient']
}
return response()->noContent();
});

Manage endpoints with $vaartix->webhooks->create(), list(), get(), update(), remove(), rotateSecret(), deliveries() and retryDelivery(). See Webhooks for the events and how retries work.