Developer guideIntegrations
Official SDKs
Official Couryo SDKs for Node.js, PHP (with Laravel) and Python (with Django), with automatic Idempotency-Key, retries, typed errors and webhook verification.
The three official SDKs do the same things the same way. Any of them saves you the repetitive work of talking to the REST API:
- Automatic
Idempotency-Keyon every POST, reused on retries: a send that timed out but went through is never sent twice. - Retries with growing waits for network errors,
5xxandrate_limited, honoringRetry-After. Errors that would fail again (domain_not_verified,daily_limit_reached...) come back right away. - Typed errors with the API's stable
code, themessagein the language you choose (pt-BRby default, oren) and therequest_idfor support. - Webhook verification (Standard Webhooks,
whsec_secret) in one call. - Emails, batch, templates, domains, contacts, events, campaigns, suppressions and webhooks.
| Language | Package | Install | Ready-made integration |
|---|---|---|---|
| Node.js 20.3+ | @couryo/node |
npm install @couryo/node |
Next.js and Express (guide) |
| PHP 8.1+ | couryo/couryo-php |
composer require couryo/couryo-php |
Laravel mail transport (guide) |
| Python 3.9+ | couryo |
pip install couryo |
Django email backend (guide) |
Using another language? The API is REST with JSON, so any HTTP client works, and SMTP covers any tool that already sends email.
Send an email#
They all read the key from COURYO_API_KEY when you pass none. Use a ck_test_ key while testing: nothing is delivered and the email shows up in the dashboard (test mode). Set language: "en" (Node), 'language' => 'en' (PHP) or language="en" (Python) to get error messages in English.
import { Couryo } from "@couryo/node";
const couryo = new Couryo({ language: "en" }); // reads COURYO_API_KEY
const { id } = await couryo.emails.send({
from: "Example Store <orders@example.com>",
to: "ana@example.com",
subject: "Order 1042 confirmed",
html: "<p>Thanks for your order!</p>",
text: "Thanks for your order!",
});<?php
require 'vendor/autoload.php';
$couryo = new Couryo\Couryo(['api_key' => getenv('COURYO_API_KEY'), 'language' => 'en']);
$email = $couryo->emails->send([
'from' => 'Example Store <orders@example.com>',
'to' => 'ana@example.com',
'subject' => 'Order 1042 confirmed',
'html' => '<p>Thanks for your order!</p>',
'text' => 'Thanks for your order!',
]);
echo $email['id']; // msg_...from couryo import Couryo
couryo = Couryo(language="en") # reads COURYO_API_KEY
email = couryo.emails.send({
"from": "Example Store <orders@example.com>",
"to": "ana@example.com",
"subject": "Order 1042 confirmed",
"html": "<p>Thanks for your order!</p>",
"text": "Thanks for your order!",
})
print(email["id"]) # msg_...Your own idempotency key#
When the same event in your system can fire twice (a repeated payment webhook, a job that runs again), pass a key that identifies the event. The second call gets the same response and nothing is sent again.
await couryo.emails.send(body, { idempotencyKey: "order-1042-confirmation" });$couryo->emails->send($body, ['idempotency_key' => 'order-1042-confirmation']);couryo.emails.send(body, idempotency_key="order-1042-confirmation")Handle errors#
import { CouryoError } from "@couryo/node";
try {
await couryo.emails.send(body);
} catch (e) {
if (e instanceof CouryoError && e.code === "domain_not_verified") {
// e.status, e.message, e.param, e.requestId
}
}use Couryo\Exception\CouryoException;
try {
$couryo->emails->send($body);
} catch (CouryoException $e) {
if ($e->getErrorCode() === 'domain_not_verified') {
// $e->getStatus(), $e->getMessage(), $e->getParam(), $e->getRequestId()
}
}from couryo import CouryoError
try:
couryo.emails.send(body)
except CouryoError as e:
if e.code == "domain_not_verified":
... # e.status, e.message, e.param, e.request_idEvery code, with cause and fix, is listed in Errors.
Verify a webhook#
Pass the raw body, exactly as received. The SDK checks the signature and the timestamp (up to 5 minutes) and returns the event.
import { verifyWebhook } from "@couryo/node";
const event = verifyWebhook({ secret: process.env.COURYO_WEBHOOK_SECRET!, headers: req.headers, body: rawBody });$event = Couryo\Webhook::verify(getenv('COURYO_WEBHOOK_SECRET'), getallheaders(), file_get_contents('php://input'));from couryo import verify_webhook
event = verify_webhook(os.environ["COURYO_WEBHOOK_SECRET"], request.headers, raw_body)Payload format in Webhooks.
Contacts, events and campaigns#
await couryo.contacts.create({ email: "ana@example.com", attributes: { name: "Ana", plan: "pro" }, tags: ["customer"] });
await couryo.events.track({ contact: { email: "ana@example.com" }, name: "user.signed_up" });
const campaign = await couryo.campaigns.create({ name: "October news", audience: { type: "all" }, template_id: "news" });
await couryo.campaigns.preflight(campaign.id); // real count and review before sendingEvents start the sequences with that trigger; campaigns get a check before they go out. PHP and Python have the same methods, named in each language's style.
Open source#
The SDKs are MIT licensed. The Brazilian email templates (Pix, boleto, password reset and more) also ship as a standalone React Email package that works with any provider.