# Delivrie > Delivrie is an email API for developers: send transactional and marketing email from your own domain, receive replies, and get delivery events as webhooks. REST API at https://delivrie.io/api (Bearer API keys), an official Node.js/TypeScript SDK (`npm install delivrie`), an SMTP relay at smtp.delivrie.io, and a remote MCP server at https://delivrie.io/api/mcp. The API matches Resend's request and response shapes. Key facts: send with POST https://delivrie.io/api/emails and `Authorization: Bearer $DELIVRIE_API_KEY`, JSON `{ from, to, subject, html | text }`. `from` must be on a verified domain (or onboarding@delivrie.io for tests to your team and *@test.delivrie.io). Errors are JSON `{ statusCode, name, message }`. Send an Idempotency-Key header so retries never double-send. Never call the API from browser code. --- --- # Introduction What Delivrie is, how the API works, and where to start: base URL, authentication, SDKs, SMTP and the MCP server. Delivrie is an email platform for developers. Send transactional email (receipts, sign-in codes, password resets) and marketing email (broadcasts, automations) from your own domain, receive replies in an inbox, and get every delivery event as a webhook. You can reach Delivrie four ways. They all share the same account, limits, logs and webhooks. | Way in | Use it when | Start here | | --- | --- | --- | | REST API | You're writing code in any language | [Quickstart](/docs/quickstart) | | Node.js SDK | You're on Node.js, Bun, Deno, Workers or Vercel Edge | [Node.js SDK](/docs/sdks) | | SMTP relay | A framework, CMS or plugin already sends email over SMTP | [SMTP](/docs/smtp) | | MCP server | An AI assistant should send email or manage your account | [MCP server](/docs/mcp) | ## The API in one minute - **Base URL:** `https://delivrie.io/api` - **Authentication:** send your API key in the `Authorization` header: `Authorization: Bearer dlv_your_key`. Create keys in the dashboard under [API keys](https://delivrie.io/api-keys). - **Format:** JSON in, JSON out. Send `Content-Type: application/json` on requests with a body. - **Errors:** every error is JSON with `statusCode`, `name` and `message`. See [Errors](/docs/errors). - **Lists:** list endpoints return `{ "object": "list", "data": [...] }` and take `limit` (1 to 100, default 50) and `offset`. When `has_more` is `true`, ask for the next page with a higher `offset`. - **Safe retries:** send an `Idempotency-Key` header with every email send. See [Idempotency and retries](/docs/idempotency). - **Server side only:** never call the API from browser code; your key would be visible to anyone. See [CORS](/docs/cors). ```bash cURL curl -X POST https://delivrie.io/api/emails \ -H "Authorization: Bearer $DELIVRIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "Acme ", "to": "delivered@test.delivrie.io", "subject": "Hello from Delivrie", "html": "

It works!

" }' ``` ```ts Node.js import { Delivrie } from "delivrie"; const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY); const { data, error } = await delivrie.emails.send({ from: "Acme ", to: "delivered@test.delivrie.io", subject: "Hello from Delivrie", html: "

It works!

", }); ``` ```python Python import os, requests res = requests.post( "https://delivrie.io/api/emails", headers={"Authorization": f"Bearer {os.environ['DELIVRIE_API_KEY']}"}, json={ "from": "Acme ", "to": "delivered@test.delivrie.io", "subject": "Hello from Delivrie", "html": "

It works!

", }, ) print(res.status_code, res.json()) ``` `onboarding@delivrie.io` is a shared sender for testing: it can email the [test addresses](/docs/test-mode) and the people on your team. To email anyone else, [verify your own domain](/docs/domains) and send from it. The response is `{ "id": "" }`. Delivery happens in the background; follow it in the dashboard under Emails, with `GET /emails/:id`, or with [webhooks](/docs/webhooks). ## Building with an AI coding assistant? Point it at [delivrie.io/llms.txt](https://delivrie.io/llms.txt). It lists every guide and endpoint as plain Markdown, so Claude, Cursor, Codex, v0 and other tools read the docs directly. [Build with AI](/docs/build-with-ai) has prompts you can paste and a rules file for your project. ## Coming from Resend? The API uses the same request and response shapes, so most code keeps working. See [Migrate from Resend](/docs/migrate-from-resend). --- # Quickstart Send your first email in five minutes: create an account and API key, test with the shared sender, then verify your own domain. This takes about five minutes. You'll send a test email right away, then set up your own domain so you can email anyone. ## 1. Create an account Sign up at [delivrie.io/signup](https://delivrie.io/signup). The Free plan includes 4,000 emails a month (up to 133 a day), so you don't need a card. ## 2. Create an API key Open [API keys](https://delivrie.io/api-keys) in the dashboard and click **Create API key**. - **Full access** keys can call every endpoint. Use them for scripts and back-office tools. - **Sending access** keys can only send email, optionally from one domain. Use them in production apps. Keys start with `dlv_` and are shown once. Store the key in an environment variable, never in your code: ```bash .env DELIVRIE_API_KEY=dlv_your_key ``` ## 3. Send a test email You can send before you verify a domain. Use the shared sender `onboarding@delivrie.io` as the `from` address, and send to either: - the **test addresses** `delivered@test.delivrie.io`, `bounced@test.delivrie.io` and `complained@test.delivrie.io`. They behave like real inboxes and fire real events. See [Test addresses](/docs/test-mode). - **yourself and your teammates**: the email addresses of people on your Delivrie team. ```bash cURL curl -X POST https://delivrie.io/api/emails \ -H "Authorization: Bearer $DELIVRIE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-1" \ -d '{ "from": "Acme ", "to": "delivered@test.delivrie.io", "subject": "My first Delivrie email", "html": "

It works

Sent with Delivrie.

" }' ``` ```ts Node.js // npm install delivrie import { Delivrie } from "delivrie"; const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY); const { data, error } = await delivrie.emails.send( { from: "Acme ", to: "delivered@test.delivrie.io", subject: "My first Delivrie email", html: "

It works

Sent with Delivrie.

", }, { idempotencyKey: "quickstart-1" }, ); if (error) throw new Error(`${error.name}: ${error.message}`); console.log("Sent", data.id); ``` ```python Python import os, requests res = requests.post( "https://delivrie.io/api/emails", headers={ "Authorization": f"Bearer {os.environ['DELIVRIE_API_KEY']}", "Idempotency-Key": "quickstart-1", }, json={ "from": "Acme ", "to": "delivered@test.delivrie.io", "subject": "My first Delivrie email", "html": "

It works

Sent with Delivrie.

", }, ) res.raise_for_status() print("Sent", res.json()["id"]) ``` A successful send returns `200` with `{ "id": "..." }`. Open [Emails](https://delivrie.io/emails) in the dashboard to watch it move from queued to sent to delivered. > **Note:** If you get `422 validation_error`, read the `message`. It says exactly what's wrong, for example a missing field or a `from` domain that isn't verified yet. ## 4. Verify your domain To send to anyone, add a domain you own. 1. Open [Domains](https://delivrie.io/domains/new) and add a subdomain such as `mail.acme.com`. A subdomain keeps your main domain's reputation separate from your app's email. 2. Add the DNS records Delivrie shows you at your DNS provider: a DKIM `TXT` record, and an `MX` and `TXT` record on the `send` subdomain for SPF and bounces. 3. Click **Verify DNS records**. Most providers update within minutes. Then send from any address on that domain, like `orders@mail.acme.com`, to anyone. [Domains and DNS](/docs/domains) explains each record, and [Why isn't my domain verifying?](/docs/domain-not-verifying) covers the usual fixes. ## 5. Next steps - Track deliveries, bounces and opens with [webhooks](/docs/webhooks). - Reuse designs with [templates](/docs/templates). - Find your framework under SDKs and frameworks: [Next.js](/docs/nextjs), [Express](/docs/express), [Python](/docs/python), [PHP and Laravel](/docs/php), [Ruby and Rails](/docs/ruby), [Go](/docs/go), [Rust](/docs/rust), [Supabase](/docs/supabase), [Cloudflare Workers](/docs/cloudflare-workers). - Before you go live, read the [Deliverability guide](/docs/deliverability). --- # Build with AI Use Delivrie with Claude, Cursor, Codex, v0, Lovable, Bolt and other AI tools: llms.txt, Markdown docs, a rules file for your repo, prompts to paste and the MCP server. AI coding tools write better email code when they read the right docs. Delivrie publishes everything in formats they understand, and gives your assistant a way to act on your account directly. ## Give your assistant the docs | URL | What it is | | --- | --- | | [delivrie.io/llms.txt](https://delivrie.io/llms.txt) | An index of every guide and API endpoint, with links to Markdown versions | | [delivrie.io/llms-full.txt](https://delivrie.io/llms-full.txt) | All guides and the full API reference in one Markdown file | | `https://delivrie.io/docs/.md` | Any docs page as Markdown, for example [/docs/quickstart.md](https://delivrie.io/docs/quickstart.md) or [/docs/api/send-email.md](https://delivrie.io/docs/api/send-email.md) | Every docs page also has **Copy page** (Markdown for your clipboard) and **Open in Claude** buttons. In Cursor, add `https://delivrie.io/llms-full.txt` under **Settings → Indexing & Docs → Add Doc**, then type `@Docs` in chat and pick Delivrie. In Claude, ChatGPT or v0, paste the link into your message and ask the assistant to read it. ## Add a rules file to your project Assistants follow project rules on every request. Save this as `AGENTS.md` (Codex, Cursor, Copilot, Jules and most agents), `CLAUDE.md` (Claude Code) or `.cursor/rules/delivrie.mdc` (Cursor): ```markdown AGENTS.md ## Email: Delivrie - Send email with Delivrie. Docs: https://delivrie.io/llms.txt (Markdown: append .md to any docs URL). - Node/TypeScript: use the `delivrie` npm package. `new Delivrie(process.env.DELIVRIE_API_KEY)`. Calls return `{ data, error }` and never throw for API errors: always check `error`. - Other languages: POST https://delivrie.io/api/emails with `Authorization: Bearer $DELIVRIE_API_KEY` and JSON `{ from, to, subject, html | text }`. - Read the key from the DELIVRIE_API_KEY environment variable. Never hard-code it and never call Delivrie from browser code: send from a server route, server action or edge function. - `from` must be on a verified domain, as "Name ". Ours is: mail.example.com - Pass an idempotency key derived from the business event (e.g. `order-${order.id}-receipt`) so retries never send twice. - In tests and local development, send to delivered@test.delivrie.io (or bounced@ / complained@). - Delivery results arrive as signed webhooks (Standard Webhooks). Verify with `verifyWebhook` from the `delivrie` package before trusting a payload. ``` Change `mail.example.com` to your verified domain. ## Prompts to paste Start a new chat with one of these, filling in the brackets. ```text Add email Add transactional email to this app with Delivrie (https://delivrie.io/llms-full.txt). Send a [welcome email] when [a user signs up]. Use the `delivrie` npm package (or plain fetch to https://delivrie.io/api/emails if this isn't a JS project), read DELIVRIE_API_KEY from the environment, send only from server code, use "[App name] <[hello@mail.example.com]>" as the sender, and pass an idempotency key based on the user id. Show me where to add the env var. ``` ```text Webhooks Using Delivrie webhooks (https://delivrie.io/docs/webhooks.md), add an endpoint at [/api/webhooks/delivrie] that verifies the signature with `verifyWebhook` from the `delivrie` package using DELIVRIE_WEBHOOK_SECRET, reads the raw request body before parsing it, and [marks the user's email as invalid] on email.bounced and email.complained. Return 200 quickly and 400 when the signature is wrong. ``` ```text Migrate Migrate this project from Resend to Delivrie following https://delivrie.io/docs/migrate-from-resend.md. Keep behaviour the same, change only what the guide says, and list the environment variables I need to set. ``` ```text v0 / Lovable / Bolt Add a contact form that emails me through Delivrie. Create a server route (never call Delivrie from the browser) that POSTs to https://delivrie.io/api/emails with Authorization: Bearer ${DELIVRIE_API_KEY}, from "[Site] <[forms@mail.example.com]>", to "[me@example.com]", reply_to the visitor's email, and the message as text. Show a success state, and an error state using the API's error message. ``` ## Let the assistant use your account (MCP) The [MCP server](/docs/mcp) at `https://delivrie.io/api/mcp` gives assistants 64 tools: send email, check delivery, add domains, manage contacts, broadcasts and templates, read logs and reply to inbox threads. You approve which team and scopes it can use, and you can revoke it under Settings → Connected apps. ```bash Claude Code claude mcp add --transport http delivrie https://delivrie.io/api/mcp ``` ```json Cursor // ~/.cursor/mcp.json { "mcpServers": { "delivrie": { "url": "https://delivrie.io/api/mcp" } } } ``` ```toml Codex # ~/.codex/config.toml [mcp_servers.delivrie] command = "npx" args = ["-y", "mcp-remote", "https://delivrie.io/api/mcp"] ``` ```text v0 v0 → Settings → MCP connections → Add custom connection Name Delivrie URL https://delivrie.io/api/mcp Header Authorization: Bearer dlv_your_key ``` Then ask things like "Send a test email to delivered@test.delivrie.io from onboarding@delivrie.io", "Why did my last email to ada@example.com bounce?" or "Add mail.example.com and tell me which DNS records to create". [MCP server](/docs/mcp) has setup for Claude Desktop, ChatGPT, Antigravity and VS Code, plus the full tool list. ## Check your assistant's work AI-written email code usually fails in one of these ways: - **The key is in client code.** Search the project for `dlv_` and for `delivrie.io/api` in files that run in the browser. Move those calls to the server. - **Errors are ignored.** With the SDK, check `error` on every call. With `fetch`, check `res.ok`; the body's `message` explains the problem. - **No idempotency key,** so a retried request sends the email twice. - **Webhook body parsed before verification.** Signature checks need the raw body exactly as received. - **Unverified sender.** `from` must use a domain verified in your Delivrie team, or `onboarding@delivrie.io` while testing. --- # Migrate from Resend Move from Resend to Delivrie without rewriting your app: same request shapes, point the Resend SDK at Delivrie with one environment variable, or switch to the delivrie package. Delivrie's API uses the same endpoints, field names and response shapes as Resend's, so most apps switch by changing two environment variables. Plan for about 30 minutes, most of it waiting for DNS. ## 1. Add your domain to Delivrie Add the same sending domain in [Domains](https://delivrie.io/domains/new). Delivrie gives you a new DKIM record (its own selector, so it sits next to Resend's without conflict) and SPF records for a bounce subdomain. > **Important:** Resend and Delivrie both use the `send` subdomain for bounces by default, and a name can only have one SPF record. To keep Resend working while you switch, set a different bounce subdomain when you add the domain: in the dashboard under **Advanced options**, or with `"return_path": "bounces"` in [`POST /domains`](/docs/api/create-domain). You can remove Resend's records after the switch. Leave out `region` when you create the domain, or use `eu-west-1`. Other regions aren't available yet. ## 2. Create a Delivrie API key Create one in [API keys](https://delivrie.io/api-keys). Delivrie keys start with `dlv_`. `full_access` and `sending_access` work the same way as in Resend. ## 3. Point your code at Delivrie ### Option A: keep the Resend SDK The official Resend SDKs can send to another API address. Set two environment variables and redeploy: | SDK | Set these | | --- | --- | | Node.js (`resend`) | `RESEND_BASE_URL=https://delivrie.io/api` and `RESEND_API_KEY=dlv_...` | | Python (`resend`) | `RESEND_API_URL=https://delivrie.io/api` and `RESEND_API_KEY=dlv_...` | | PHP (`resend/resend-php`, Laravel) | `RESEND_BASE_URL=https://delivrie.io/api` and `RESEND_API_KEY=dlv_...` | | Go (`resend-go`) | `RESEND_BASE_URL=https://delivrie.io/api/` (keep the trailing slash) | No code changes. `resend.emails.send`, `resend.batch.send`, `resend.emails.get`, scheduling, tags and idempotency keys behave the same. The Node SDK's `react` option keeps working too, because the SDK turns your React Email component into HTML before sending. ### Option B: switch to the Delivrie SDK (Node.js) ```bash npm uninstall resend && npm install delivrie ``` ```ts Before (Resend) import { Resend } from "resend"; const resend = new Resend(process.env.RESEND_API_KEY); const { data, error } = await resend.emails.send({ from: "Acme ", to: ["ada@example.com"], subject: "Welcome", html: "

Welcome to Acme

", }); ``` ```ts After (Delivrie) import { Delivrie } from "delivrie"; const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY); const { data, error } = await delivrie.emails.send({ from: "Acme ", to: ["ada@example.com"], subject: "Welcome", html: "

Welcome to Acme

", }); ``` Same `{ data, error }` result, same field names. React Email works the same way too: pass `react: ` and keep `@react-email/render` installed. ### Option C: plain HTTP Replace `https://api.resend.com` with `https://delivrie.io/api` and your `re_...` key with a `dlv_...` key. Paths and bodies stay the same. ## 4. Update webhooks Create a webhook in [Webhooks](https://delivrie.io/webhooks) with the same events. Event names (`email.sent`, `email.delivered`, `email.bounced`, `email.complained`, `email.opened`, `email.clicked`, `email.received` and the rest) and the `{ type, created_at, data }` body are the same. Signatures use the same HMAC scheme, but the headers are named `webhook-id`, `webhook-timestamp` and `webhook-signature` (the [Standard Webhooks](https://www.standardwebhooks.com) names) instead of `svix-id`, `svix-timestamp` and `svix-signature`. Use `verifyWebhook` from the `delivrie` package, or any Standard Webhooks library, with your new `whsec_` secret. See [Webhooks](/docs/webhooks) for code. ## What's different | Resend | Delivrie | | --- | --- | | `https://api.resend.com` | `https://delivrie.io/api` | | Keys start with `re_` | Keys start with `dlv_` | | Attachments by `path` (a URL) | Send base64 `content` instead; `path` isn't supported yet | | Audiences (`/audiences/:id/contacts`) | Contacts live at `/contacts`; group them with [segments](/docs/contacts) | | Broadcasts to an audience | Broadcasts go to a `segment_id` | | `svix-*` webhook headers | `webhook-*` headers, same signing | | Several sending regions | `eu-west-1` today | | `onboarding@resend.dev` for testing | `onboarding@delivrie.io`, plus [test addresses](/docs/test-mode) | Things Delivrie adds: an [SMTP relay](/docs/smtp) on ports 587 and 465, an [inbox](/docs/inbox) for replies, [automations](/docs/automations) triggered by events, an [MCP server](/docs/mcp) for AI assistants, and billing in naira or dollars. ## Move your contacts and templates - **Contacts:** export a CSV from Resend and import it in [Contacts → Import](https://delivrie.io/contacts/import), or loop over them with [`POST /contacts`](/docs/api/create-contact). - **Templates:** copy the HTML into a new [template](/docs/templates). Variables use the same `{{{name}}}` syntax, then publish and send with `template: { id: "your-alias", variables: {...} }`. In broadcasts, `{{{RESEND_UNSUBSCRIBE_URL}}}` keeps working as an alias for `{{{unsubscribe_url}}}`. - **Suppressions:** add addresses that bounced or complained on Resend with [`POST /suppressions`](/docs/api/add-suppression), so you don't email them again. ## Switch-over checklist 1. Domain verified in Delivrie, with a non-`send` bounce subdomain if Resend is still live. 2. `dlv_` key stored in your production environment. 3. Send a test to `delivered@test.delivrie.io` from your production sender and check it in [Emails](https://delivrie.io/emails). 4. Webhook endpoint updated for the new header names and secret, and **Send test event** returns 200. 5. Deploy. Watch [Emails](https://delivrie.io/emails) and [Logs](https://delivrie.io/logs) for an hour. 6. Remove Resend's DNS records and keys when you're happy. --- # API keys Create, scope, store and rotate Delivrie API keys: full access vs sending access, domain-restricted keys, and what to do if a key leaks. Every API request needs a key in the `Authorization` header: ```http Authorization: Bearer dlv_your_key ``` ## Create a key In the dashboard, open [API keys](https://delivrie.io/api-keys) and click **Create API key**, or call [`POST /api-keys`](/docs/api/create-api-key) with a full access key: ```bash curl -X POST https://delivrie.io/api/api-keys \ -H "Authorization: Bearer $DELIVRIE_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Production web app", "permission": "sending_access" }' ``` The response contains the key in `token`. It's shown once: Delivrie stores only a hash, so copy it straight into your secret store. ## Permissions | Permission | Can do | Use for | | --- | --- | --- | | `full_access` | Every endpoint: domains, contacts, webhooks, keys, logs and sending | Scripts, back-office tools, CI | | `sending_access` | Only sending: `POST /emails`, `POST /emails/batch`, rescheduling, canceling and forwarding emails, `POST /events` and inbox replies | Production apps | Add `domain_id` to a sending key to limit it to one domain. Sending from any other domain then fails with `403 restricted_api_key`. Calling an endpoint a sending key can't use also returns `403 restricted_api_key`. ## Store keys safely - Keep keys in environment variables or your platform's secret store (Vercel, Netlify, Fly, Supabase, Cloudflare and GitHub Actions all have one). Name it `DELIVRIE_API_KEY`; the Node.js SDK reads it automatically. - Never put a key in browser or mobile app code, a public repo, or a URL. - Use a separate key per app and environment, so you can revoke one without breaking the others. ## Rotate or revoke Create the new key, deploy it, then delete the old one in [API keys](https://delivrie.io/api-keys) (or with `DELETE /api-keys/:id`). Deletion takes effect immediately; requests with the old key get `401 invalid_api_key`. > **Warning:** If a key leaks, delete it straight away, then check [Logs](https://delivrie.io/logs) for requests you don't recognise. Every API request is logged, along with the key that made it. ## Keys for AI assistants You don't need to give an assistant your API key. Connect it to the [MCP server](/docs/mcp) instead: you approve the team and scopes in your browser, and can revoke access under Settings → Connected apps. --- # Plans and limits Email allowances, daily limits, domains, webhooks, data retention and team seats on Free, Starter, Pro and Scale, plus what happens when you reach a limit. | | Free | Starter | Pro | Scale | | --- | --- | --- | --- | --- | | Emails a month | 4,000 | 20,000 | 60,000 | 100,000 | | Daily limit | 133 | None | None | None | | Over the allowance | Sending pauses (or uses credits) | Keeps sending, billed per 1,000 | Keeps sending, billed per 1,000 | Keeps sending, billed per 1,000 | | Domains | 1 | 10 | 10 | 1,000 | | Webhook endpoints | 1 | 10 | 10 | Unlimited | | Team members | 1 | 5 | 5 | Unlimited | | Email content kept (sent and received) | 30 days | 30 days | 30 days | 30 days | | API request logs kept | 30 days | 30 days | 30 days | 30 days | Prices are on the [pricing page](https://delivrie.io/pricing). Enterprise plans with custom volumes, retention and support are available through [sales](https://delivrie.io/contact-sales). ## What counts as an email Each email you send through the API or SMTP counts as one, whether it goes to one recipient or several in `to`, `cc` and `bcc`. A batch of 50 counts as 50. Sends to the [test addresses](/docs/test-mode) count too. Broadcasts are billed through the marketing plan instead. Check where you are with `GET /usage` or in [Billing](https://delivrie.io/billing): ```json { "object": "usage", "plan": "free", "emails_this_month": 1840, "monthly_allowance": 4000, "emails_today": 37, "daily_limit": 133, "credits_emails": 0, "overage_after_allowance": false } ``` ## When you reach a limit **Free plan.** At 133 emails in a day or 4,000 in a month, sends fail with `429 daily_quota_exceeded` or `429 monthly_quota_exceeded`. If you've bought pay-as-you-go credits, Delivrie uses them instead and keeps sending. One credit is one email, and credits never expire. Daily limits reset at midnight UTC; monthly limits on the 1st (UTC). **Paid plans.** Sending never stops at the allowance. Emails over it are billed per 1,000 at the end of the month. To put a ceiling on that, set a spending cap in [Billing](https://delivrie.io/billing); a send that would go past it fails with `429 spending_cap_reached`. **Domains and webhooks.** Adding more than your plan allows fails with `403 plan_limit_reached`. > **Tip:** Treat `429` quota errors as "stop and alert someone", not "retry in a loop". Retrying won't succeed until the limit resets or the plan changes. --- # Node.js SDK The official delivrie npm package for Node.js and TypeScript: install, configuration, results and errors, retries, every method, attachments and webhook verification. The `delivrie` package is the official SDK for Node.js and TypeScript. It has no dependencies and uses only web-standard APIs (`fetch` and Web Crypto), so the same code runs on Node.js 18+, Bun, Deno, Cloudflare Workers and Vercel Edge. Source: [github.com/codeddesigner/delivrie](https://github.com/codeddesigner/delivrie/tree/main/sdk/node). ```bash npm npm install delivrie ``` ```bash pnpm pnpm add delivrie ``` ```bash yarn yarn add delivrie ``` ```bash bun bun add delivrie ``` ## Create a client ```ts import { Delivrie } from "delivrie"; const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY); ``` If you leave out the key, the client reads `DELIVRIE_API_KEY` from the environment. On edge runtimes without `process.env` (Cloudflare Workers, for example), pass the key from your bindings: `new Delivrie(env.DELIVRIE_API_KEY)`. CommonJS works too: `const { Delivrie } = require("delivrie")`. | Option | Default | What it does | | --- | --- | --- | | `baseUrl` | `https://delivrie.io/api` | API address | | `timeout` | `30000` | Milliseconds before a request gives up | | `maxRetries` | `2` | Retries for server errors and network failures | | `fetch` | global `fetch` | Your own fetch, for tests or older runtimes | | `headers` | none | Extra headers on every request | ```ts const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY, { timeout: 10_000, maxRetries: 3 }); ``` ## Send an email ```ts const { data, error } = await delivrie.emails.send({ from: "Acme ", to: "ada@example.com", subject: "Your order is confirmed", html: "

Order #1042 is on its way.

", replyTo: "support@acme.com", tags: [{ name: "category", value: "receipt" }], }, { idempotencyKey: "order-1042-receipt" }); ``` Field names match the API (`reply_to`, `scheduled_at`). `replyTo` and `scheduledAt` are accepted as aliases, and `scheduledAt` can be a `Date`. Every field is described in [Send an email](/docs/send-email). ## React Email Pass a [React Email](https://react.email) component as `react`, and the SDK renders it to HTML and plain text for you: ```bash npm install @react-email/components @react-email/render ``` ```tsx import { Welcome } from "./emails/welcome"; await delivrie.emails.send({ from: "Acme ", to: "ada@example.com", subject: "Welcome to Acme", react: , }); ``` `react` works in `emails.batch` too. If you also pass `html`, the `html` wins. Without `@react-email/render` installed, the call returns an error named `react_render_error` explaining what to install. On edge runtimes that can't load it at run time, render first with `await render()` and pass the result as `html`. ## Results and errors Every method resolves to `{ data, error }`, and exactly one of them is `null`. API errors never throw, so there's no `try`/`catch` to forget: ```ts const { data, error } = await delivrie.emails.send(email); if (error) { console.error(error.statusCode, error.name, error.message); return; } console.log(data.id); ``` `error.name` is the API's error name, like `validation_error` or `monthly_quota_exceeded` (see [Errors](/docs/errors)). Problems that never reach the API come back with `statusCode: 0` and the name `network_error`, `timeout` or `aborted`. ## Retries and idempotency - `GET` and `DELETE` requests are retried up to `maxRetries` times on `5xx` responses and network failures, with exponential backoff. - Sends (`POST`) are retried **only when you pass `idempotencyKey`**. The key makes the retry return the original email instead of sending a second one. ```ts await delivrie.emails.send(email, { idempotencyKey: `welcome-${user.id}` }); ``` Cancel a request with an `AbortSignal`: `delivrie.emails.send(email, { signal: AbortSignal.timeout(5000) })`. ## Attachments Pass `content` as a base64 string, a Node.js `Buffer` or a `Uint8Array`; the SDK encodes bytes for you. Up to 40 MB per email. ```ts import { readFile } from "node:fs/promises"; await delivrie.emails.send({ from: "Billing ", to: "ada@example.com", subject: "Your invoice", text: "Your invoice is attached.", attachments: [{ filename: "invoice-1042.pdf", content: await readFile("invoice-1042.pdf") }], }); ``` ## Methods | Resource | Methods | | --- | --- | | `emails` | `send(email, opts?)`, `batch(emails, opts?)`, `get(id)`, `list({ limit, offset, direction })`, `listReceived()`, `update(id, { scheduled_at })`, `cancel(id)`, `forward(id, { to, from, note })` | | `domains` | `create({ name })`, `get(id)`, `verify(id)`, `update(id, {...})`, `list()`, `remove(id)` | | `apiKeys` | `create({ name, permission, domain_id })`, `list()`, `remove(id)` | | `webhooks` | `create({ endpoint, events })`, `list()`, `deliveries(id)`, `test(id)`, `replay(deliveryId)`, `verify(opts)` | | `contacts` | `create({...})`, `get(idOrEmail)`, `update(idOrEmail, {...})`, `list({...})`, `remove(idOrEmail)` | | `segments` | `create({ name })`, `list()`, `remove(id)`, `addContacts(id, contacts)` | | `topics`, `contactProperties` | `list()` | | `broadcasts` | `create({...})`, `get(id)`, `list()`, `send(id, { scheduled_at })`, `cancel(id)`, `recipients(id)`, `remove(id)` | | `templates` | `create({...})`, `get(idOrAlias)`, `list()`, `publish(id)` | | `suppressions` | `add(email \| emails)`, `list()`, `remove(email)` | | `events` | `send({ event, email, data })`, `list()` | | `automations` | `list()`, `get(id)`, `create({...})`, `runs(id)`, `publish(id)`, `pause(id)`, `resume(id)` | | `inbox.threads` | `list({...})`, `get(id)`, `update(id, {...})`, `reply(id, { text \| html })` | | `inbox.labels` | `list()` | | `metrics` | `get({ days, domain_id })` | | `logs` | `list({ limit, offset })`, `get(id)` | | `usage` | `get()` | For anything not wrapped yet, call the API directly with the same auth, retries and error handling: ```ts const { data, error } = await delivrie.request("GET", "/emails/received", undefined, { query: { limit: 10 } }); ``` ## Verify webhooks `verifyWebhook` checks a webhook's signature against the raw request body and your `whsec_` secret, rejects requests older than five minutes, and returns the parsed event. It's `async` because it uses Web Crypto. ```ts import { verifyWebhook } from "delivrie"; export async function POST(req: Request) { try { const event = await verifyWebhook({ payload: await req.text(), headers: req.headers, secret: process.env.DELIVRIE_WEBHOOK_SECRET!, }); // event.type, event.data return new Response("ok"); } catch { return new Response("invalid signature", { status: 400 }); } } ``` With Express, read the raw body with `express.raw({ type: "application/json" })` and pass `req.body` and `req.headers`. More in [Webhooks](/docs/webhooks). ## TypeScript Types ship with the package: `SendEmailRequest`, `Email`, `Domain`, `Contact`, `WebhookEvent`, `DelivrieErrorResponse` and more. ```ts import type { SendEmailRequest, WebhookEvent } from "delivrie"; ``` --- # Next.js Send email from Next.js with Delivrie: App Router route handlers, Server Actions, React Email templates, webhooks and deploying on Vercel. Send from server code only: route handlers, Server Actions or `getServerSideProps`. Never from client components, because the API key would ship to the browser. ## 1. Install and add your key ```bash npm install delivrie ``` ```bash .env.local DELIVRIE_API_KEY=dlv_your_key ``` On Vercel, add the same variable under **Project → Settings → Environment Variables**. Don't prefix it with `NEXT_PUBLIC_`, which would expose it to the browser. Create one client to share: ```ts lib/delivrie.ts import { Delivrie } from "delivrie"; export const delivrie = new Delivrie(process.env.DELIVRIE_API_KEY); ``` ## 2a. Send from a route handler ```ts app/api/send/route.ts import { delivrie } from "@/lib/delivrie"; export async function POST(req: Request) { const { email, name } = await req.json(); const { data, error } = await delivrie.emails.send({ from: "Acme ", to: email, subject: `Welcome to Acme, ${name}`, html: `

Hi ${name}, thanks for signing up.

`, }, { idempotencyKey: `welcome-${email}` }); if (error) return Response.json({ error: error.message }, { status: error.statusCode || 500 }); return Response.json({ id: data.id }); } ``` > **Warning:** Escape anything users type before putting it in `html`, or use React Email (below), which escapes for you. ## 2b. Or from a Server Action ```tsx app/contact/page.tsx import { delivrie } from "@/lib/delivrie"; async function sendMessage(formData: FormData) { "use server"; const { error } = await delivrie.emails.send({ from: "Website ", to: "team@acme.com", replyTo: String(formData.get("email")), subject: "New contact form message", text: String(formData.get("message")), }); if (error) throw new Error(error.message); } export default function Page() { return (