SDKs
Official, MIT-licensed client libraries for Node.js, Python, PHP, Go and Ruby — plus a command-line tool and an MCP server. Consistent interface across every language.
Every SDK wraps the REST API with idiomatic types, an QUOLLE_API_KEY environment-variable fallback, safe retry-with-backoff, and webhook signature verification. Prefer raw HTTP? See the API Reference.
Install
| Language | Install | Source |
|---|---|---|
| Python | pip install quolle | quolle-python |
| PHP | composer require quolle/quolle-php | quolle-php |
| Go | go get github.com/Quolle-main/quolle-go | quolle-go |
| Ruby | gem install quolle | quolle-ruby |
| Node.js / TypeScript | Coming soon @quolle/sdk | quolle-nodejs |
| CLI | Coming soon quolle | quolle-cli |
| MCP server | Coming soon @quolle/mcp | quolle-mcp |
The walkthrough below previews the Node.js SDK's interface. It's the same across every language — see each SDK's README (linked above) for idiomatic examples.
Initialize
Import Quolle and pass your API key as an object. The constructor takes { apiKey } — not a bare string.
import { Quolle } from "@quolle/sdk";
const quolle = new Quolle({ apiKey: process.env.QUOLLE_API_KEY! });The QuolleConfig type also accepts optional baseURL (default: https://api.quolle.com) and timeout (default: 30000 ms).
Send an email
const { id } = await quolle.emails.send({
from: "hello@mail.yourdomain.com",
to: "customer@example.com",
subject: "Welcome!",
html: "<h1>Thanks for signing up</h1>",
});
console.log("Queued:", id);Send payload fields
| Field | Type | Required | Notes |
|---|---|---|---|
from | string | Yes | Must be from a verified domain |
to | string | string[] | Yes | Up to 50 recipients (To + CC combined) |
subject | string | Yes | |
html | string | Yes | Email body as HTML |
text | string | No | Plain-text fallback |
replyTo | string | No | |
scheduledAt | string | No | ISO 8601 future datetime |
metadata | object | No | Arbitrary key/value pairs |
Returns: Promise<{ id: string }>
Scheduled send
Pass a future ISO 8601 datetime in scheduledAt. The email is queued but not delivered until that time.
const { id } = await quolle.emails.send({
from: "hello@mail.yourdomain.com",
to: "customer@example.com",
subject: "Your weekly digest",
html: "<p>Here's what happened this week.</p>",
scheduledAt: "2026-12-25T09:00:00.000Z",
});Idempotency
Set an idempotency key before calling send. If you retry the same key, the cached response is returned and the email is not sent again. The key is scoped to your account and expires after 24 hours.
quolle.emails.setIdempotencyKey("order_invoice_12345");
const { id } = await quolle.emails.send({
from: "billing@mail.yourdomain.com",
to: "customer@example.com",
subject: "Invoice #1234",
html: "<p>Your invoice is attached.</p>",
});Batch sending
Send up to 100 emails in one call. Each item in the array is a full SendEmailPayload.
const recipients = [
{ email: "alice@example.com", name: "Alice" },
{ email: "bob@example.com", name: "Bob" },
];
const { ids, queued } = await quolle.emails.sendBatch(
recipients.map(r => ({
from: "hello@mail.yourdomain.com",
to: r.email,
subject: `Hi ${r.name}, welcome!`,
html: `<p>Hi ${r.name}, thanks for signing up.</p>`,
}))
);
console.log(`Queued ${queued} emails`, ids);Returns: Promise<{ queued: number; ids: string[] }>
queued is the count of emails accepted into the queue. ids is the array of email IDs in the same order as the input.
Retrieve an email
Fetch an email by its ID to check current status, open/click counts, and timestamps. Returns a single Email object. The lookup is tenant-scoped — returns 404 if the ID doesn't belong to your account.
const email = await quolle.emails.get("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
console.log(email.status); // "queued" | "sending" | "sent" | "delivered" | "bounced" | "failed"
console.log(email.opensCount); // number of opens tracked
console.log(email.sentAt); // ISO 8601 string, or undefined if not yet sentReturns: Promise<Email>
| Field | Type | Notes |
|---|---|---|
id | string | UUID |
from | string | |
to | string | Comma-joined if multiple recipients |
subject | string | |
status | string | "queued" | "sending" | "sent" | "delivered" | "bounced" | "failed" |
opensCount | number | |
clicksCount | number | |
openedAt | string? | ISO 8601, if opened |
clickedAt | string? | ISO 8601, if clicked |
sentAt | string? | ISO 8601, if dispatched |
createdAt | string | ISO 8601 |
messageId | string? | Provider message ID |
provider | string? | Delivery provider used |
Cancel a scheduled email
Cancel a queued email before it is dispatched. Works on emails that have not yet been handed to the delivery provider — typically scheduled sends still in the queue.
const { message } = await quolle.emails.cancel("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
console.log(message); // "Email cancelled successfully"Returns: Promise<{ message: string }>
Returns 404 if the email doesn't exist in your account. The REST equivalent is DELETE /v1/emails/:id/cancel.
Templates
Pass template (slug) and optional variables to send() instead of html. The two modes are mutually exclusive — TypeScript enforces this at compile time.
const { id } = await quolle.emails.send({
from: "hello@mail.yourdomain.com",
to: "customer@example.com",
template: "welcome-email", // replace with your template's slug
variables: { firstName: "Amaka", planName: "Starter" }, // match your template's placeholders
});The subject is taken from the template's saved subject field. Pass subject on the payload to override it. See Templates for the full template field reference.
Error handling
The SDK throws a QuolleError when the API returns a non-2xx status. Import it alongside Quolle to branch on specific status codes.
import { Quolle, QuolleError } from "@quolle/sdk";
const quolle = new Quolle({ apiKey: process.env.QUOLLE_API_KEY! });
try {
const { id } = await quolle.emails.send({
from: "hello@mail.yourdomain.com",
to: "customer@example.com",
subject: "Welcome!",
html: "<h1>Welcome</h1>",
});
console.log("Queued:", id);
} catch (err) {
if (err instanceof QuolleError) {
console.error(err.statusCode, err.message);
// err.data may contain extra fields (e.g. { limit, used, plan } for 402)
}
}See Errors for every status code, exact message strings, and handling guidance.
The same send, in every language
The interface mirrors across SDKs — here's a basic send in each:
from quolle import Quolle
quolle = Quolle("qle_...") # or set QUOLLE_API_KEY
quolle.emails.send(
from_="hello@mail.yourdomain.com",
to="customer@example.com",
subject="Welcome!",
html="<h1>Thanks for signing up</h1>",
)$quolle = new \Quolle\Client('qle_...'); // or QUOLLE_API_KEY
$quolle->emails->send([
'from' => 'hello@mail.yourdomain.com',
'to' => 'customer@example.com',
'subject' => 'Welcome!',
'html' => '<h1>Thanks for signing up</h1>',
]);client := quolle.New("qle_...") // or QUOLLE_API_KEY
client.Emails.Send(context.Background(), quolle.SendParams{
From: "hello@mail.yourdomain.com",
To: quolle.To("customer@example.com"),
Subject: "Welcome!",
HTML: "<h1>Thanks for signing up</h1>",
})quolle = Quolle.new(api_key: "qle_...") # or QUOLLE_API_KEY
quolle.emails.send(
from: "hello@mail.yourdomain.com",
to: "customer@example.com",
subject: "Welcome!",
html: "<h1>Thanks for signing up</h1>"
)Full examples — templates, attachments, batch, scheduling, webhook verification — live in each SDK's README (linked in the install table above). For raw HTTP, see the API Reference.