Developer guideIntegrations
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.
Two ways to send and one to receive. First: verify the domain 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.
- Create a Header Auth credential (in Credentials > Add credential): name
Authorization, valueBearer ck_live_.... - 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 |
- In the body, use expressions with the previous node's data:
{
"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, 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#
- Add a Webhook node with method
POSTand copy the Production URL. - In the Couryo dashboard, under Webhooks, create a webhook with that URL and the events you care about (for example,
bouncedandcomplained). Keep thewhsec_secret. - Activate the workflow. Each event arrives with
typeanddata(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:
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.
Workflow ideas#
- Order paid in your system → HTTP Request to Couryo with an
Idempotency-Keyequal to the order ID. bouncedevent → flag the contact in your CRM and tell the team in the sales channel.- Sign-up event →
POST https://api.couryo.com/v1/eventswith{ "contact": { "email": ... }, "name": "user.signed_up" }to start a sequence.