# 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.

Source: https://couryo.com/en/docs/sdks

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](https://couryo.com/en/docs/sending.md):

- **Automatic `Idempotency-Key`** on every POST, reused on retries: a send that timed out but went through is never sent twice.
- **Retries with growing waits** for network errors, `5xx` and `rate_limited`, honoring `Retry-After`. Errors that would fail again (`domain_not_verified`, `daily_limit_reached`...) come back right away.
- **Typed errors with the API's stable `code`**, the `message` in the language you choose (`pt-BR` by default, or `en`) and the `request_id` for 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](https://couryo.com/en/docs/nextjs.md)) |
| PHP 8.1+ | `couryo/couryo-php` | `composer require couryo/couryo-php` | Laravel mail transport ([guide](https://couryo.com/en/docs/laravel.md)) |
| Python 3.9+ | `couryo` | `pip install couryo` | Django email backend ([guide](https://couryo.com/en/docs/django.md)) |

Using another language? The API is REST with JSON, so any HTTP client works, and [SMTP](https://couryo.com/en/docs/smtp.md) 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](https://couryo.com/en/docs/test-mode.md)). Set `language: "en"` (Node), `'language' => 'en'` (PHP) or `language="en"` (Python) to get error messages in English.

```ts title="Node.js"
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 title="PHP"
<?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_...
```

```python title="Python"
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.

```ts title="Node.js"
await couryo.emails.send(body, { idempotencyKey: "order-1042-confirmation" });
```

```php title="PHP"
$couryo->emails->send($body, ['idempotency_key' => 'order-1042-confirmation']);
```

```python title="Python"
couryo.emails.send(body, idempotency_key="order-1042-confirmation")
```

## Handle errors

```ts title="Node.js"
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
  }
}
```

```php title="PHP"
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()
    }
}
```

```python title="Python"
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_id
```

Every code, with cause and fix, is listed in [Errors](https://couryo.com/en/docs/errors.md).

## 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.

```ts title="Node.js"
import { verifyWebhook } from "@couryo/node";

const event = verifyWebhook({ secret: process.env.COURYO_WEBHOOK_SECRET!, headers: req.headers, body: rawBody });
```

```php title="PHP"
$event = Couryo\Webhook::verify(getenv('COURYO_WEBHOOK_SECRET'), getallheaders(), file_get_contents('php://input'));
```

```python title="Python"
from couryo import verify_webhook

event = verify_webhook(os.environ["COURYO_WEBHOOK_SECRET"], request.headers, raw_body)
```

Payload format in [Webhooks](https://couryo.com/en/docs/webhooks.md).

## Contacts, events and campaigns

```ts title="Node.js"
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 sending
```

Events start the [sequences](https://couryo.com/en/docs/sequences.md) with that trigger; [campaigns](https://couryo.com/en/docs/campaigns.md) 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.
