# Send email from n8n

> Send email with Couryo from n8n with the HTTP Request node (API with idempotency) or the Send Email node (SMTP), and receive delivery events in a workflow with the Webhook node.

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

Two ways to send and one to receive. First: [verify the domain](https://couryo.com/en/docs/domains.md) in Couryo and create an API key with the `send` scope.

## Send with the HTTP Request node (recommended)

Through the API, each send returns the message ID and you can use an idempotency key: if the workflow runs again for the same item, the email does not go out twice.

1. Create a **Header Auth** credential (in **Credentials > Add credential**): name `Authorization`, value `Bearer ck_live_...`.
2. Add an **HTTP Request** node:

| Field | Value |
|---|---|
| Method | `POST` |
| URL | `https://api.couryo.com/v1/emails` |
| Authentication | Generic Credential Type > Header Auth (the credential above) |
| Send Headers | on: `Idempotency-Key` = `{{ 'order-' + $json.order_id }}`, and `Accept-Language` = `en` |
| Send Body | on, Body Content Type `JSON`, Specify Body `Using JSON` |

3. In the body, use expressions with the previous node's data:

```json title="Body (JSON)"
{
  "from": "Example Store <orders@example.com>",
  "to": "{{ $json.email }}",
  "subject": "Order {{ $json.order_id }} confirmed",
  "html": "<p>Hi {{ $json.name }}, we got your order.</p>",
  "text": "Hi {{ $json.name }}, we got your order.",
  "tags": { "source": "n8n" }
}
```

The `202` response carries `{ "id": "msg_...", "status": "queued" }`. On error, the body carries `error.code` and `error.message`: turn on **On Error > Continue (using error output)** on the node to handle it in the workflow.

With a [saved template](https://couryo.com/en/docs/templates.md), replace `subject`, `html` and `text` with `"template": "order-confirmed"` and `"variables": { "name": "{{ $json.name }}" }`.

## Send with the Send Email node (SMTP)

Create an **SMTP** credential:

| Field | Value |
|---|---|
| User | `couryo` |
| Password | your API key (`ck_live_...`) |
| Host | `smtp.couryo.com` |
| Port | `587` (STARTTLS) or `465` |
| SSL/TLS | off on 587; on for 465 |

On the **Send Email** node, the **From Email** must be on the verified domain.

## Receive delivery events

1. Add a **Webhook** node with method `POST` and copy the **Production URL**.
2. In the Couryo dashboard, under **Webhooks**, create a webhook with that URL and the events you care about (for example, `bounced` and `complained`). Keep the `whsec_` secret.
3. Activate the workflow. Each event arrives with `type` and `data` (`email_id`, `recipient`, `smtp_code`, `explanation`...).

### Check the signature

To make sure the event came from Couryo, turn on the **Raw Body** option on the Webhook node and add a **Code** node right after it:

```js title="Code (JavaScript)"
const crypto = require("crypto");

const secret = $env.COURYO_WEBHOOK_SECRET; // whsec_...
const headers = $input.first().json.headers;
const body = (await this.helpers.getBinaryDataBuffer(0, "data")).toString("utf8");

const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const expected = crypto
  .createHmac("sha256", key)
  .update(`${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${body}`)
  .digest("base64");

const received = String(headers["webhook-signature"] || "").split(" ").map((s) => s.split(",")[1]);
const fresh = Math.abs(Date.now() / 1000 - Number(headers["webhook-timestamp"])) <= 300;
if (!fresh || !received.includes(expected)) throw new Error("invalid signature");

return [{ json: JSON.parse(body) }];
```

On self-hosted n8n, the `crypto` module must be allowed (`NODE_FUNCTION_ALLOW_BUILTIN=crypto`) and the `COURYO_WEBHOOK_SECRET` variable must exist in the environment. The full format is in [Webhooks](https://couryo.com/en/docs/webhooks.md).

## Workflow ideas

- Order paid in your system → **HTTP Request** to Couryo with an `Idempotency-Key` equal to the order ID.
- `bounced` event → flag the contact in your CRM and tell the team in the sales channel.
- Sign-up event → `POST https://api.couryo.com/v1/events` with `{ "contact": { "email": ... }, "name": "user.signed_up" }` to start a [sequence](https://couryo.com/en/docs/sequences.md).
