# Send emails with n8n

> Send from an n8n workflow with the HTTP Request node.

## What you'll build

An n8n workflow that sends email through PostShiba with the HTTP Request node. You can also send with the Send Email node over SMTP, and start workflows from PostShiba delivery events with the Webhook node.

## Before you start

- An API key with **Full access**. See [Create an API key](/docs/create-an-api-key). Support keys cannot send.
- A verified sending domain. See [Add and verify a domain](/docs/add-a-domain). `from` must be an address on it.
- A **Transactional** cluster that is ready to send. One-off notifications from a workflow belong on a transactional cluster, and the type is locked after create. See [Clusters](/docs/dashboard/clusters/introduction).

## Install

Nothing to install. The HTTP Request, Send Email, Webhook, and Code nodes are built into n8n.

## Send your first email

1. Add an **HTTP Request** node to your workflow.
2. On the node's **Parameters** tab, select **Import cURL**, paste this command, and select **Import**. n8n fills in the method, URL, headers, and body.

```bash n8n-curl.sh
curl -X POST https://app.postshiba.com/api/v1/emails \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "hello@mail.example.com",
    "to": "you@example.com",
    "subject": "Your receipt",
    "text": "Thanks for your order."
  }'
```

3. Move the key into a credential so it is not stored in the node. Delete the `Authorization` header from **Send Headers**. Set **Authentication** to **Generic Credential Type** and **Generic Auth Type** to **Header Auth**, then create a credential with **Name** `Authorization` and **Value** `Bearer YOUR_API_KEY`.
4. Map values from earlier nodes with expressions. For example, set `to` to `{{ $json.email }}` and `subject` to `{{ $json.subject }}`.
5. Run the node. A queued send returns `201` with `"queued": true`. The message then shows under Events in the dashboard.

`to` takes one address or a comma-separated string. Every field is on [Send emails with HTTP](/docs/send-with-http). To send on a specific cluster, add an `X-Capsule-Cluster-Id` header.

## Send over SMTP instead

The **Send Email** node sends through an SMTP credential. Create an SMTP username and password first: open your cluster in the dashboard, go to its SMTP credentials, and choose **Create SMTP credential**. Copy the password right away, because it is shown once.

1. Add a **Send Email** node and create a new SMTP credential with these values. The credential starts with port `465` and **SSL/TLS** on, so change both.

```text n8n-smtp.txt
User:              smtp_9
Password:          YOUR_PASSWORD
Host:              smtp.postshiba.com
Port:              587
SSL/TLS:           off
Disable STARTTLS:  off
Client Host Name:  (leave empty)
```

2. Save the credential. n8n tests the connection when you save.
3. Set **From Email** to an address on your verified domain, such as `hello@mail.example.com`, and fill in **To Email**, **Subject**, and the body.

With **SSL/TLS** off, the connection starts plain on port `587` and upgrades with STARTTLS. Keep **Disable STARTTLS** off, because PostShiba accepts `AUTH PLAIN` only after STARTTLS. Leave the node's **Ignore SSL Issues (Insecure)** option off. Every SMTP setting is on [Send emails with SMTP](/docs/send-with-smtp).

## React to delivery events

PostShiba POSTs each delivery event to a URL as a JSON array in the SendGrid Event Webhook format. A **Webhook** node can receive it and start a workflow.

1. Add a **Webhook** node. Set **HTTP Method** to `POST`, and under **Add Option** turn on **Raw Body**.
2. Copy the **Production URL**. PostShiba needs a public `https` URL, and n8n registers the production URL when you publish the workflow.
3. In PostShiba, open **Webhooks**, click **Add endpoint**, paste the URL, and save. Copy the **Signing secret** from the endpoint page. See [Managing webhooks](/docs/webhooks/introduction).
4. Add a **Code** node after the Webhook node, in **Run Once for All Items** mode with JavaScript. It checks the signature and outputs one item per event.

```javascript n8n-verify.js
const crypto = require('crypto')

const secret = 'YOUR_SIGNING_SECRET'
const { headers } = $input.first().json
const raw = await this.helpers.getBinaryDataBuffer(0, 'data')

const timestamp = headers['x-capsule-timestamp'] || ''
const given = (headers['x-capsule-signature'] || '').replace(/^sha256=/, '')
const expected = crypto.createHmac('sha256', secret)
  .update(`${timestamp}.`)
  .update(raw)
  .digest('hex')

const a = Buffer.from(expected)
const b = Buffer.from(given)
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
  throw new Error('Invalid PostShiba signature')
}

return JSON.parse(raw.toString('utf8')).map((event) => ({ json: event }))
```

5. Click **Send test event** on the endpoint in PostShiba. The test event has `"test": true`.

The signature covers the exact bytes of the body, so the check reads the raw body that **Raw Body** stores as binary data, not the parsed JSON. With **Respond** set to **Immediately**, PostShiba gets `200` right away, and a bad signature stops the workflow at the Code node.

n8n Cloud allows `crypto` in the Code node. On self-hosted n8n, set `NODE_FUNCTION_ALLOW_BUILTIN=crypto`. If your instance uses task runners, set it on the task runners. Fields on each event are on [Event types](/docs/webhooks/event-types). Use `sg_event_id` to drop duplicates when a delivery is retried.

## Troubleshooting

| What you see | What to do |
| --- | --- |
| `401` `Invalid token or no user signed in` | The API key is missing or mistyped. Check that the credential **Value** starts with `Bearer `, and that the imported `Authorization` header is removed. |
| `403` `forbidden` | Support keys cannot send. Use a Full access key. |
| `403` `domain_unverified` or `domain_suspended` | Use a `from` address on a verified domain. Resume a paused domain from [Managing domains](/docs/dashboard/domains/manage-domains). |
| `403` `kyc_required` or `550 kyc_required` | Your team is pending production approval. Until then, send only to team members, live inbox addresses, and inbox forward-to addresses. See [Does PostShiba require production approval?](/docs/knowledge-base/does-capsule-require-production-approval) |
| `429` `throttled` or `451 4.7.0 hourly send limit reached` | The cluster is at its hourly throttle or 24-hour cap, and the message was not kept. Retry later. See [What happens at your cap](/docs/dashboard/clusters/what-happens-at-your-cap). |
| `429` `rate_limited` | More than 300 send requests in one minute on one API key. Use the HTTP Request node's **Batching** option to slow down. |
| The SMTP credential test fails with an SSL or timeout error | Set **Port** to `587` and turn **SSL/TLS** off. Port 465 is not offered. |
| `550 unverified domain` or `550 from header not aligned with verified domain` | Set **From Email** to an address on your verified domain. |
| `403` `suppressed`, `403` `firewall`, `550 suppressed`, or `550 firewall: <rule>` | The recipient is on the [suppression list](/docs/dashboard/emails/email-suppressions) or failed an [email firewall](/docs/dashboard/emails/email-firewall) check. |
| The Code node throws `Invalid PostShiba signature` | Check the signing secret, and that **Raw Body** is on in the Webhook node. |
| The Code node can't load `crypto` | On self-hosted n8n, set `NODE_FUNCTION_ALLOW_BUILTIN=crypto` and restart. |

## Next steps

- [Send emails with HTTP](/docs/send-with-http) for every field on the send request.
- [Unique args](/docs/dashboard/emails/unique-args) to get your own ids back on delivery webhooks.
- [Verify webhook requests](/docs/webhooks/verify-webhooks-requests)
- [Event types](/docs/webhooks/event-types)
