Search by

quolle / quolle-php

quolle

Official PHP SDK for the Quolle email API

v1.0.0 2026-07-25 06:26 UTC

This package is auto-updated.

Last update: 2026-08-25 06:54:04 UTC


README

Official PHP client for the Quolle email API.

Install

composer require quolle/quolle-php

Requires PHP 7.4+ with the curl and json extensions.

Quick start

require 'vendor/autoload.php';

$quolle = new \Quolle\Client('qle_...'); // or set QUOLLE_API_KEY

$result = $quolle->emails->send([
    'from'    => 'hello@mail.yourdomain.com',
    'to'      => 'customer@example.com',
    'subject' => 'Welcome!',
    'html'    => '<h1>Thanks for signing up</h1>',
]);

echo "Queued: " . $result['id'];

Sending

Multiple recipients

$quolle->emails->send([
    'from'    => 'hello@mail.yourdomain.com',
    'to'      => ['a@example.com', 'b@example.com'],
    'subject' => 'Announcement',
    'html'    => '<p>Hello everyone</p>',
]);

Templates

$quolle->emails->send([
    'from'      => 'hello@mail.yourdomain.com',
    'to'        => 'customer@example.com',
    'template'  => 'welcome-email',
    'variables' => ['firstName' => 'Amaka', 'planName' => 'Starter'],
]);

Scheduled send

$quolle->emails->send([
    'from'        => 'hello@mail.yourdomain.com',
    'to'          => 'customer@example.com',
    'subject'     => 'Your weekly digest',
    'html'        => '<p>Here is what happened this week.</p>',
    'scheduledAt' => '2026-12-25T09:00:00.000Z',
]);

Idempotency

$quolle->emails->send([
    'from'    => 'billing@mail.yourdomain.com',
    'to'      => 'customer@example.com',
    'subject' => 'Invoice #1234',
    'html'    => '<p>Your invoice is attached.</p>',
], 'order_invoice_12345');

Batch

Up to 100 emails in one all-or-nothing request:

$result = $quolle->emails->sendBatch([
    ['from' => 'hello@mail.yourdomain.com', 'to' => 'a@example.com',
     'subject' => 'Hi Alice', 'html' => '<p>Hi Alice</p>'],
    ['from' => 'hello@mail.yourdomain.com', 'to' => 'b@example.com',
     'subject' => 'Hi Bob', 'html' => '<p>Hi Bob</p>'],
]);

echo "Queued {$result['queued']}";

Attachments

Add an attachments array — each with filename, base64 content, and an optional contentType. Up to 20 files, 10 MB total.

$quolle->emails->send([
    'from'    => 'billing@mail.yourdomain.com',
    'to'      => 'customer@example.com',
    'subject' => 'Your invoice',
    'html'    => '<p>Invoice attached.</p>',
    'attachments' => [
        [
            'filename'    => 'invoice.pdf',
            'content'     => base64_encode(file_get_contents('invoice.pdf')),
            'contentType' => 'application/pdf',
        ],
    ],
]);

Retrieve & cancel

$email = $quolle->emails->get('a1b2c3d4-...');
echo $email['status']; // queued | sending | sent | delivered | bounced | failed

$quolle->emails->cancel('a1b2c3d4-...'); // only while status == "scheduled"

Error handling

use Quolle\QuolleException;

try {
    $quolle->emails->send([
        'from' => 'hello@mail.yourdomain.com', 'to' => 'customer@example.com',
        'subject' => 'Welcome!', 'html' => '<h1>Welcome</h1>',
    ]);
} catch (QuolleException $e) {
    echo $e->statusCode;      // e.g. 402
    echo $e->getMessage();    // e.g. "Monthly limit reached"
    print_r($e->data);        // extra fields, e.g. ['limit' => 3000]
}

Testing your integration

Send to a reserved test address to simulate any outcome without touching your sending reputation: delivered@test.quolle.com, bounced@test.quolle.com, complained@test.quolle.com, suppressed@test.quolle.com.

Verifying webhooks

Confirm an incoming webhook really came from Quolle. Pass the raw request body, the Quolle-Signature header, and your signing secret (whsec_…):

use Quolle\QuolleException;

$rawBody = file_get_contents('php://input');
try {
    $event = $quolle->webhooks->verify(
        $rawBody,
        $_SERVER['HTTP_QUOLLE_SIGNATURE'],
        'whsec_your_signing_secret'
    );
    // $event['event'] === 'email.delivered'
} catch (QuolleException $e) {
    http_response_code(400);
}

HMAC-SHA256 with a 5-minute timestamp window (replay protection).

Automatic retries

Transient failures — HTTP 429 (rate limit) and 5xx, plus network errors — are retried automatically with exponential backoff, honoring the Retry-After header. To avoid double-sending, a POST is only retried on a 5xx/network error when you pass an idempotency key; a 429 is always safe to retry (the request was never processed). Tune with max_retries in the options array (default 3).

License

MIT

quolle-php