# Ranla — full documentation bundle Generated for AI agents. Canonical index: https://docs.ranla.ai/llms.txt OpenAPI: https://docs.ranla.ai/openapi.yaml --- # What is Ranla Ranla is an **AI growth marketer for SaaS**. It instruments your product, writes lifecycle email, sends it through infrastructure you control, measures revenue per send, and reports back in dollars. You built the product. Ranla makes every signup count: onboarding sequences, retention campaigns, weekly reviews, and guardrails that keep sends inside limits you set. ## Transactional + lifecycle on one stack Most teams start with one problem: **the app needs to send email today** (password resets, magic links, receipts). Ranla includes that send path on the free tier: verify a domain, create an API key, call `POST /emails`. When you **hire Ranla**, the same account unlocks the growth workspace: campaigns, automations, event-driven sequences, and an agent that proposes and runs lifecycle work without you wiring every flow by hand. | Layer | What it is | |-------|------------| | **Send API** | Transactional and campaign email via REST, SDK, or SMTP | | **Events** | Product signals Ranla reads (`user.created`, `trial.started`, …) | | **Automations** | Event-triggered sequences with send steps | | **Agent** | Proposes campaigns, drafts copy, schedules sends (with approval modes) | | **Reporting** | Revenue attribution tied to sends | ## Free send vs hire Ranla | | Free (TX tier) | Hire Ranla | |---|----------------|------------| | **Who** | Every new account | Paid growth bands | | **Send API** | 3,000 emails/mo · 100/day · 1 domain | Included sends with monthly ceiling per band | | **Growth workspace** | Upsell | Agent, campaigns, autopilot | | **Typical use** | Auth email, sandbox, self-test | Lifecycle email that runs without you | See [Instrument your product](./instrument-quickstart.md) to wire events first, or [Quickstart: send email](./quickstart.md) if you only need `POST /emails` today. ## What Ranla is not - Not a separate mail pipe you integrate twice — one API, one domain verify, one webhook stream. - Not an ads or SEO tool — Ranla owns **email** for your product lifecycle. - Not a mailbox provider — you send from domains you verify; we operate the delivery infrastructure. ## Next steps - [Instrument your product](./instrument-quickstart.md) — SDK, events, what Ranla sees on day one - [Quickstart: send email](./quickstart.md) — API key, domain DNS, first send - [Events API](./api/events.md) · [Automations API](./api/automations.md) - Product site: [ranla.ai](https://ranla.ai) · Dashboard: [app.ranla.ai](https://app.ranla.ai) --- # Instrument your product Wire Ranla into your SaaS in about ten minutes. This path is for founders and growth leads who want lifecycle email to run on product data — not only `POST /emails` for password resets. **Only need auth email today?** Skip to [Quickstart: send email](./quickstart.md). --- ## What you are setting up 1. **Account + API key** on [app.ranla.ai](https://app.ranla.ai) 2. **Node SDK** (or REST) in your app 3. **Product events** Ranla uses for segments and automations 4. **Verified sending domain** so campaigns send from your brand 5. **Optional:** first automation or agent setup in the dashboard Ranla reads events from your app. Campaigns and automations react to those events. Sends go through the same API as transactional mail. --- ## 1. Create an account and API key 1. Sign up at [app.ranla.ai](https://app.ranla.ai). 2. Open **Get started** (`/overview`) or **API Keys → Create**. 3. Copy the `rnl_…` secret once. ```bash export RANLA_API_KEY=rnl_your_key_here ``` --- ## 2. Install the SDK ```bash npm install @supersend/ranla ``` ```ts import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY!) ``` **Other languages** — same API, Ranla defaults (`https://api.ranla.ai`): | Language | Install | |----------|---------| | Python | `pip install ranla` · `from ranla import Ranla` | | Go | `go get github.com/Super-Send/supersendtx-sdks/go/ranla` | | PHP | `composer require ranla/ranla` | | Ruby | `gem install ranla` · `Ranla::Client` | The legacy `supersendtx` packages on each registry remain supported for existing integrations. --- ## 3. Track product events Send events when something meaningful happens in your product. Ranla uses these for segments, automations, and the growth agent. ```ts await client.events.trigger({ name: 'user.created', user_id: 'user_123', email: 'ada@yourdomain.com', data: { plan: 'trial', source: 'signup' }, idempotencyKey: 'evt_user_created_123', }) ``` Common first events: | Event | When to fire | |-------|----------------| | `user.created` | Account or workspace created | | `user.activated` | Finished onboarding or first value action | | `subscription.started` | Paid or trial started | | `subscription.churned` | Cancelled or expired | REST equivalent: ```bash curl -X POST https://api.ranla.ai/events \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: evt_user_created_123" \ -d '{ "name": "user.created", "user_id": "user_123", "email": "ada@yourdomain.com", "data": { "plan": "trial" } }' ``` See [Events API](./api/events.md) for fields, idempotency, and automation matching. --- ## 4. Verify a sending domain Lifecycle and transactional email must send from a domain you control. 1. **Domains → Add domain** in the dashboard. 2. Add DNS records (Cloudflare one-click if offered). 3. **Verify DNS** on the domain page. Until DNS is verified, use sandbox self-test sends to your account email. See [Quickstart: send email](./quickstart.md) §4 for the full DNS table. --- ## 5. Send a test email (optional but recommended) Confirm the pipe works before you rely on automations: ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "noreply@yourdomain.com", "to": "you@yourdomain.com", "subject": "Ranla pipe check", "html": "
Send path works.
" }' ``` Free tier: 3,000 emails/mo · 100/day · 1 domain. Add a payment method to unlock production sends to other recipients. --- ## 6. Turn on lifecycle (hire Ranla) The growth workspace (agent, campaign cycles, autopilot) unlocks when you **hire Ranla** on a paid band. Until then, `/growth` shows an upsell — you can still send events and use the send API on the free tier. In the dashboard after hire: 1. Open **Growth** and review what Ranla sees from your events. 2. Approve or edit a proposed campaign. 3. Optionally wire an [automation](./api/automations.md) for `user.created` → welcome sequence. Use **Copy setup prompt** on Get started to hand the same flow to Cursor or Claude ([MCP server](./ai/mcp.md)). --- ## Next steps - [What is Ranla](./what-is-ranla.md) - [Automations](./api/automations.md) · [Templates](./api/templates.md) - [Quickstart: send email](./quickstart.md) - [Agent skill notes](./ai/agent-skill.md) · [ranla-mcp](./ai/mcp.md) --- # Ranla — Quickstart Send your first transactional email in about five minutes. Migrating from another provider? Start with [Resend](./migration.md), [Postmark](./migration/postmark.md), [Amazon SES](./migration/ses.md), or [SendGrid](./migration/sendgrid.md), then mirror send + webhook flows from the hosted [API Explorer](https://docs.ranla.ai/api-explorer). Building auth email? See [password reset](./guides/password-reset-emails.md), [Supabase Auth](./guides/supabase.md), and [Clerk / Auth.js](./guides/auth-provider-email.md). Using React Email components? See [Send React Email](./guides/react-email.md). **Hosted docs:** [docs.ranla.ai](https://docs.ranla.ai) **Dashboard:** [app.ranla.ai](https://app.ranla.ai) **API:** `https://api.ranla.ai` --- ## For AI agents - **Index:** [docs.ranla.ai/llms.txt](https://docs.ranla.ai/llms.txt) - **Full bundle:** [docs.ranla.ai/llms-full.txt](https://docs.ranla.ai/llms-full.txt) - **OpenAPI:** [docs.ranla.ai/openapi.yaml](https://docs.ranla.ai/openapi.yaml) - **Agent skill notes:** [docs.ranla.ai/ai/agent-skill](https://docs.ranla.ai/ai/agent-skill) - **MCP server:** [docs.ranla.ai/ai/mcp](https://docs.ranla.ai/ai/mcp) (`ranla-mcp` on npm) - **AI app builders:** [docs.ranla.ai/builders](https://docs.ranla.ai/builders) (Lovable, Replit, Bolt, Base44, v0 prompts) Use the dashboard **Get started** page to copy a personalized setup prompt with your API key and sandbox rules. --- ## 1. Create an account 1. Open the dashboard at [app.ranla.ai](https://app.ranla.ai). 2. Sign up with email + password (or email code if you prefer). 3. If using email code: enter the code emailed to you. --- ## 2. Send a sandbox test (fastest path) On **Get started** (`/overview`), choose one of two doors: ### Set it up here (dashboard) 1. Click **Send test email**. We create an API key if needed and send to your account email (sandbox sender before DNS; your verified domain after). 2. Check your inbox for the test message. 3. Copy the `rnl_…` secret if shown (once). No domain or DNS required for the first sandbox send. After you verify a domain, the same button self-tests from that domain (still to your account email on Sandbox). ### Use with Cursor or Claude 1. On **Get started**, click **Copy setup prompt** or **Install in Cursor**. 2. Paste the prompt into your editor, or add the `ranla-mcp` MCP server with your API key. 3. Let the agent wire `POST /emails` (or the SDK) into your app and run the sandbox self-test. See [`docs/ai/agent-skill.md`](./ai/agent-skill.md) and [`docs/ai/mcp.md`](./ai/mcp.md). --- ## 3. Create an API key (if you skipped the dashboard flow) 1. Go to **API Keys → Create** (or use an existing `rnl_…` key). 2. Copy the secret — it is shown **once**. ```bash export RANLA_API_KEY=rnl_your_key_here ``` --- ## 4. Verify a sending domain (DNS setup — self-test from your domain on free) Add and verify your domain on Sandbox to complete DNS setup. On the free plan you can **self-test from your verified domain to your account email** to confirm DNS and branding. **Free production** (send to other recipients) unlocks after a **verified domain and a payment method on file** — we do not charge until you upgrade. Caps: 3,000 emails/mo · 100/day · 1 domain. Upgrade to Pro or Scale for more volume. ### Dashboard (recommended) 1. Go to **Domains → Add domain** and enter your domain (e.g. `yourdomain.com`). 2. On the domain page (or in guided setup): - If your DNS is on Cloudflare: click **Sign in to Cloudflare** when offered — authorize once and we add the records. - Or **Settings → Integrations** → paste a Cloudflare API token (Zone DNS Edit) → Save, then **Apply DNS (Cloudflare)**. - If your DNS is at GoDaddy: switch to **GoDaddy**, paste an API key + secret for a one-time apply. - Otherwise paste the shown TXT records at your DNS host (merge SPF if you already have one). 3. Click **Verify DNS**. 4. Optional after verification: set **Delivery settings** on the domain detail page for: - open tracking - click tracking - `tls_mode` (`opportunistic` or `enforced`) 5. Check the built-in DMARC/BIMI guidance on the same page before you roll out branded inbox features. | Type | Purpose | |------|---------| | TXT `_supersendtx.yourdomain.com` | Domain ownership | | TXT `{selector}._domainkey.yourdomain.com` | DKIM (domain signing key) | | CNAME `rp.yourdomain.com` | Return path → `rp.supersendtx.com` | | TXT `yourdomain.com` | SPF (`include:spf.supersendtx.com`) | | TXT `_dmarc.yourdomain.com` | DMARC | Ownership, SPF, DKIM, and return-path are required to verify. Verify also confirms mail-server DKIM readiness so sends are signed with your domain (not the shared pool). Local Docker auto-verifies when `RANLA_DEV_AUTO_VERIFY=true`. If a domain is already attached to another Ranla team, `POST /domains` returns a 409 with claim instructions instead of creating a duplicate owner. ### CLI (from your editor / terminal) Save a Cloudflare token under **Settings → Integrations** first (recommended). Then: ```bash export RANLA_API_KEY=rnl_your_key_here npx -y --package=ranla-cli -- ranla domains add yourdomain.com # Uses the Cloudflare token stored in Ranla (no local CLOUDFLARE_API_TOKEN needed) npx -y --package=ranla-cli -- ranla domains apply yourdomain.com --provider cloudflare npx -y --package=ranla-cli -- ranla domains verify yourdomain.com ``` Optional override: set `CLOUDFLARE_API_TOKEN` locally to apply with a machine token instead of the dashboard one. Vercel uses `VERCEL_API_TOKEN` and optional `VERCEL_TEAM_ID` the same way. GoDaddy can use stored dashboard credentials or local `GODADDY_API_KEY` / `GODADDY_API_SECRET`. --- ## 5. Send from your app Pick one integration method below. Replace `you@yourdomain.com` with an address on your **verified** domain. ### Sandbox self-test (before DNS) Use the shared sandbox sender on `mail.supersendtx.com`, but only send to **your own account email**: ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "noreply@mail.supersendtx.com", "to": "you@example.com", "subject": "Sandbox check", "html": "Ranla sandbox works.
" }' ``` Once your domain is verified, you can self-test from your own domain to your account email: ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "you@example.com", "subject": "Verified domain check", "html": "My domain works.
" }' ``` Add a payment method at **Settings → Billing** to unlock Free production (send to other recipients). Upgrade to Pro or Scale for more volume. ### curl (production) ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello from Ranla", "html": "It works.
" }' ``` ### Node.js SDK ```bash npm install @supersend/ranla ``` ```ts import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY!) const { id, status } = await client.emails.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Hello from Ranla', html: 'It works.
', }) console.log(id, status) ``` ### CLI ```bash RANLA_API_KEY=$RANLA_API_KEY npx -y --package=ranla-cli -- ranla emails send \ --from you@yourdomain.com \ --to user@example.com \ --subject "Hello from Ranla" \ --html "It works.
" ``` Or install globally: `npm install -g ranla-cli`, then run `@supersend/ranla emails send …`. --- ## Success response ```json { "id": "msg_abc123…", "status": "sent" } ``` --- ## Plan limits When billing is enabled, Sandbox is for integration and self-test sends (hard daily and monthly caps). Until Free production is unlocked, sends are limited to your account email — from the shared sandbox sender or from a verified domain. Unlock Free production with a verified domain **and** a payment method on file (no charge until you upgrade). Paid Pool and Dedicated plans meter overage past included volume. Over Free/Sandbox limit: ```json { "error": { "message": "…", "code": "plan_limit", "upgrade_url": "/settings/billing" } } ``` HTTP **429**. Upgrade in the dashboard under **Settings → Billing**. ## Common errors | HTTP | Meaning | Fix | |------|---------|-----| | 401 | Invalid or missing API key | Check `Authorization: Bearer rnl_…` | | 403 | From domain not verified, sandbox/verified-domain recipient mismatch, or send to other recipients without Free production unlocked | Verify domain; self-test to your account email; add a payment method at Settings → Billing to unlock Free production | | 400 | Missing `from`, `to`, or `subject` | Send required fields + `html` or `text` | | 429 | Plan send limit (`code: plan_limit`) | Upgrade at Settings → Billing | | 503 | Mail delivery temporarily unavailable | Retry later or contact support | Full reference: [docs.ranla.ai/api-reference](https://docs.ranla.ai/api-reference) --- ## 6. Webhooks (optional) 1. Go to **Webhooks** in the dashboard (or use the API). 2. Add your HTTPS endpoint URL. 3. Copy the signing secret (`whsec_…`) — shown once. 4. Verify `SuperSendTX-Signature` on incoming `POST`s. ```ts await client.webhooks.create({ url: 'https://yourapp.com/webhooks/supersendtx', }) ``` See [docs.ranla.ai/api-reference](https://docs.ranla.ai/api-reference) for payload shape and signature verification. --- ## 7. Receive email (optional) Want an app inbox or agent mailbox? 1. Create a dedicated inbound subdomain such as `inbound.example.com`. 2. Add it with `inbound_enabled: true` (API) or `supersendtx domains add inbound.example.com --inbound true` (CLI). 3. Verify the domain, then publish the returned MX record. 4. Subscribe to `email.received` and/or poll `GET /received-emails`. See [`docs/api/inbound.md`](./api/inbound.md) for the full flow and the MX conflict warning. --- ## 8. Event-driven automations (optional) Ranla can trigger transactional sequences from custom events. **Dashboard:** open **Automations**, create from a starter (or blank) on the visual canvas, configure steps in the side panel (send-email can use a published **Templates** entry or inline HTML), then Activate. **CLI / API:** create and activate from a JSON file (handy in Cursor or CI): ```bash supersendtx automations create --file ./welcome.json supersendtx automations activate --id "$AUTOMATION_ID" ``` Then send the trigger event from your app: ```bash curl -X POST https://api.ranla.ai/events \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: evt_user_created_123" \ -d '{ "name": "user.created", "user_id": "user_123", "email": "user@example.com", "data": { "name": "Ada Lovelace" } }' ``` Response: ```json { "event": { "id": "ae_123", "name": "user.created", "user_id": "user_123", "email": "user@example.com", "data": { "name": "Ada Lovelace" }, "source": "api", "created_at": "2026-07-26T12:00:00.000Z" }, "matched_automations": 1, "resumed_runs": 0, "cancelled_runs": 0 } ``` See [`docs/api/automations.md`](./api/automations.md) and [`docs/api/events.md`](./api/events.md). --- ## Next steps - Docs home: [docs.ranla.ai](https://docs.ranla.ai) - API reference: [docs.ranla.ai/api-reference](https://docs.ranla.ai/api-reference) - Next.js quickstart: [docs.ranla.ai/frameworks/nextjs](https://docs.ranla.ai/frameworks/nextjs) - React Email: [docs.ranla.ai/guides/react-email](https://docs.ranla.ai/guides/react-email) - Node.js quickstart: [docs.ranla.ai/frameworks/node](https://docs.ranla.ai/frameworks/node) - Send API: [`docs/api/emails.md`](./api/emails.md) - Domains API: [`docs/api/domains.md`](./api/domains.md) - Inbound email API: [`docs/api/inbound.md`](./api/inbound.md) - Deliverability: [`docs/api/deliverability.md`](./api/deliverability.md) - Events: [`docs/api/events.md`](./api/events.md) - Templates: [`docs/api/templates.md`](./api/templates.md) - Automations: [`docs/api/automations.md`](./api/automations.md) - SMTP relay: [`docs/api/smtp.md`](./api/smtp.md) - Webhooks: [`docs/api/webhooks.md`](./api/webhooks.md) - AI onboarding: [docs.ranla.ai/llms.txt](https://docs.ranla.ai/llms.txt), [docs.ranla.ai/llms-full.txt](https://docs.ranla.ai/llms-full.txt), [`docs/ai/agent-skill.md`](./ai/agent-skill.md), [`docs/ai/mcp.md`](./ai/mcp.md) (`ranla-mcp` npm package) - OpenAPI spec: [docs.ranla.ai/openapi.yaml](https://docs.ranla.ai/openapi.yaml) --- # Authentication Ranla authenticates API requests with Bearer API keys that start with `rnl_` or `stx_`. Newly created keys still start with `rnl_`. A `rnl_` key with the same secret after the prefix authenticates as the existing key. ## Obtain a key 1. Open the [dashboard](https://app.ranla.ai). 2. Go to **API Keys → Create**. 3. Copy the secret — it is shown **once**. ```bash export RANLA_API_KEY=rnl_your_key_here ``` Keep keys server-side only. Never ship them in browser bundles or public repos. ## Request header Send the key on every management and send request: ```bash curl https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "It works.
" }' ``` ### Node SDK ```javascript import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY) ``` ### CLI ```bash export RANLA_API_KEY=rnl_your_key_here npx -y --package=ranla-cli -- ranla emails send \ --from you@yourdomain.com \ --to user@example.com \ --subject "Hello" \ --html "It works.
" ``` ## Base URLs | Surface | URL | |---------|-----| | API | `https://api.ranla.ai` | | Dashboard | `https://app.ranla.ai` | ## Unauthorized responses Missing or invalid keys return **401** with: ```json { "error": { "message": "Unauthorized" } } ``` See [Errors](/errors) for the full error shape. --- # Errors Ranla returns JSON errors that match the OpenAPI contract. ## Shape ```json { "error": { "message": "Human-readable summary", "details": {} } } ``` `details` is optional and may include field-level validation info. ## Common status codes | Status | When | |--------|------| | `400` | Invalid JSON or failed validation | | `401` | Missing/invalid `Authorization: Bearer rnl_…` | | `403` | Authenticated but not allowed (e.g. unverified sending domain) | | `404` | Resource not found | | `409` | Conflict (e.g. domain already claimed) | | `429` | Rate limited | | `502` | Upstream mail delivery failure | ## Examples ### Unverified domain (403) ```json { "error": { "message": "Sending domain is not verified" } } ``` ### Validation (400) ```json { "error": { "message": "Validation failed", "details": { "to": "Required" } } } ``` ## Idempotency Some write endpoints accept an `Idempotency-Key` header. Replays with the same key return the original success response instead of creating duplicates — see the [API Explorer](/api-explorer) and OpenAPI spec for which operations support it. --- # Migration from Resend Also migrating from other providers? - [Postmark](/migration/postmark) - [Amazon SES](/migration/ses) - [SendGrid](/migration/sendgrid) --- If you are moving an existing Resend integration, the Ranla send flow maps closely: - Bearer API keys - `POST /emails` with a familiar JSON body - Webhooks / activity for delivery outcomes The main migration steps are: 1. swap the base URL 2. swap your API key to `rnl_...` 3. verify a sending domain in Ranla 4. update any provider-specific optional fields --- ## Quick mapping | Resend | Ranla | |--------|---------------| | `https://api.resend.com` | `https://api.ranla.ai` | | `re_...` API key | `rnl_...` API key | | `POST /emails` | `POST /emails` | | `from` must be verified | `from` must be verified | | dashboard activity | dashboard activity + deliverability summary | --- ## Minimal curl diff ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "It works.
" }' ``` --- ## Accepted payload aliases Ranla accepts a few migration-friendly aliases on the HTTP send API: - `htmlBody` or `html_body` -> `html` - `textBody`, `text_body`, or `plain_body` -> `text` - `replyTo` -> `reply_to` - `scheduledAt` -> `scheduled_at` Attachment aliases are also accepted: - `filename` -> `name` - `contentType` -> `content_type` - `content` -> `data` - `contentId` -> `content_id` These aliases are meant to make low-friction migrations easier; new code should still prefer the canonical field names in docs/examples. --- ## Domain verification differences Unlike provider-managed sandbox products, Ranla expects you to verify your own sending domain for production traffic. Before DNS is ready, you can still self-test with the shared sandbox sender: ```json { "from": "noreply@mail.supersendtx.com", "to": "you@example.com", "subject": "Sandbox check", "html": "Sandbox works.
" } ``` Sandbox restriction: - the sandbox `from` domain is only allowed when all recipients match the account email on the API key owner Once your own domain is verified, switch `from` back to your branded domain. --- ## DNS apply Ranla can write DNS for you in two ways: - Cloudflare: save a token once under **Settings -> Integrations** - GoDaddy: paste one-time API credentials on the domain detail page, or pass them through the SDK/CLI locally See [`docs/api/domains.md`](./api/domains.md). --- ## SMTP If your old integration still depends on SMTP, create credentials in the dashboard (**SMTP**) or via `POST /smtp-credentials`, then connect to `smtp.supersendtx.com:587` with username `supersendtx`. Same verified domains and plan limits as the HTTP API. Prefer `POST /emails` for new app code. Details: [`docs/api/smtp.md`](./api/smtp.md). --- # Migration from Postmark Move transactional sends from Postmark to Ranla without changing your product’s email jobs — receipts, password resets, alerts, and invites still go out over `POST /emails`. --- ## Why teams switch - **HTTP API + npm SDK** with a short path from API key to first send - **Owned transactional mail infrastructure**, not a thin layer on rented cloud email - **Sandbox → Pool → Dedicated** so you can test free, run production on a paid shared transactional network, then isolate on managed servers and IPs when you need allowlists --- ## Quick mapping | Postmark | Ranla | |----------|--------------| | Server API token | `rnl_...` API key (`Authorization: Bearer`) | | `https://api.postmarkapp.com` | `https://api.ranla.ai` | | `POST /email` | `POST /emails` | | Verified Sender Signature / domain | Verified sending domain in Ranla | | Message Streams (Transactional) | Transactional-only product (no cold/marketing mix) | | Webhooks | Webhooks + dashboard activity | --- ## Minimal send ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "It works.
" }' ``` Node: ```bash npm install @supersend/ranla ``` ```js import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY) await client.emails.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Hello', html: 'It works.
', }) ``` --- ## Migration checklist 1. Create a Ranla account and API key 2. Add and verify your sending domain ([Domains](/domains)) 3. Point your app’s send client at `https://api.ranla.ai` with `rnl_...` 4. Map Postmark fields: `From` → `from`, `To` → `to`, `Subject` → `subject`, `HtmlBody` → `html`, `TextBody` → `text`, `ReplyTo` → `reply_to` 5. Reconfigure webhooks to Ranla endpoints ([Webhooks](/webhooks)) 6. Send a canary through Sandbox or your verified domain, then cut over HTTP aliases accepted today: `htmlBody` / `html_body`, `textBody` / `text_body` / `plain_body`, `replyTo` → `reply_to`. Postmark’s PascalCase `HtmlBody` is **not** accepted — map it to `html` (or `htmlBody`) in your client. New code should use canonical names (`html`, `text`, `reply_to`). --- ## Domains and DNS Production `from` addresses must use a domain verified in Ranla. Cloudflare and GoDaddy apply flows can write records for you — see [Domains](/domains). Before DNS is ready, self-test with the shared sandbox sender on `mail.supersendtx.com` (recipients limited to your account email). Details: [Quickstart](/quickstart). --- ## Related - [Migration from Resend](/migration) - [Migration from Amazon SES](/migration/ses) - [Quickstart](/quickstart) --- # Migration from Amazon SES If you send transactional mail through Amazon SES (SDK, SMTP, or a thin API on top of SES), you can move the application-facing send path to Ranla while keeping the same jobs: auth mail, receipts, alerts, and notifications. --- ## What changes | SES-oriented setup | Ranla | |--------------------|--------------| | AWS credentials / IAM | `rnl_...` API key | | Regional SES endpoint | `https://api.ranla.ai` | | `SendEmail` / `SendRawEmail` | `POST /emails` | | SES-verified identity | Ranla verified domain | | Configuration sets / event destinations | Webhooks + dashboard activity | | Shared SES IP pools (typical) | Pool (owned transactional network) or Dedicated (managed servers + IPs) | Ranla runs on **owned mail infrastructure**, not as a reseller of SES. That matters when you care who else shares the pipe — and when you later need Dedicated isolation. --- ## Minimal send ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "It works.
" }' ``` ```js import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY) await client.emails.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Hello', html: 'It works.
', }) ``` --- ## Migration checklist 1. Sign up at [app.ranla.ai](https://app.ranla.ai) and create an API key 2. Verify your sending domain ([Domains](/domains)) — SPF, DKIM, return path 3. Replace SES SDK calls with Ranla HTTP or the `supersendtx` npm package 4. Map bodies: HTML → `html`, text → `text`, reply addresses → `reply_to` 5. Point bounce/complaint/delivery handling at Ranla [webhooks](/webhooks) 6. Run parallel canary sends, then shift production traffic --- ## Sandbox vs production - **Sandbox:** send from `noreply@mail.supersendtx.com` to your account email only while integrating - **Free production:** verified domain **and** a payment method on file (no charge until you upgrade) — 3,000 emails/mo · 100/day · 1 domain on the shared transactional network (Pool) - **Pro (from $20/mo):** from 50,000 emails/mo · up to 10 domains · declining overage by tier - **Scale:** higher volume tiers with declining overage (up to 1,000 domains) - **Dedicated (from $299/mo):** managed server and IPs when you need isolation See [Pricing](https://supersendtx.com/pricing) and [Quickstart](/quickstart). --- ## SMTP SES SMTP users can move to Ranla SMTP relay: host `smtp.supersendtx.com`, port `587` (STARTTLS), username `supersendtx`, password from a dashboard SMTP credential (`stxsmtp_…`). Same verified domains, plan limits, and suppressions as the HTTP API. Prefer `POST /emails` for new app code; use SMTP when the integration only exposes an SMTP form. Details: [SMTP](/smtp). --- ## Related - [Migration from Resend](/migration) - [Migration from Postmark](/migration/postmark) - [Quickstart](/quickstart) --- # Migration from SendGrid Move transactional sends from SendGrid to Ranla — receipts, password resets, alerts, and invites still go out over `POST /emails` with a simpler JSON body than SendGrid's v3 mail send payload. --- ## Why teams switch - **Straightforward HTTP API** — one `POST /emails` instead of nested `personalizations` / `content` arrays - **Owned transactional mail infrastructure**, separate from cold/outbound reputation - **Sandbox → Pool → Dedicated** — test for free, run production on a shared transactional network, isolate on managed servers when you need allowlists --- ## Quick mapping | SendGrid | Ranla | |----------|--------------| | `SG.` API key | `rnl_…` API key (`Authorization: Bearer`) | | `https://api.sendgrid.com/v3/mail/send` | `https://api.ranla.ai/emails` | | `personalizations[].to[]` | `to` (string or array) | | `from.email` + `from.name` | `from` (single address string) | | `content[]` with `type` / `value` | `html` and/or `text` | | Dynamic templates (`template_id`) | Template `alias` on send | | Event Webhook | Webhooks + dashboard activity | | ASM / unsubscribe groups | Managed unsubscribe + suppressions | --- ## Minimal send **SendGrid (v3):** ```bash curl -X POST https://api.sendgrid.com/v3/mail/send \ -H "Authorization: Bearer $SENDGRID_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "personalizations": [{ "to": [{ "email": "user@example.com" }] }], "from": { "email": "you@yourdomain.com" }, "subject": "Hello", "content": [{ "type": "text/html", "value": "It works.
" }] }' ``` **Ranla:** ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "It works.
" }' ``` --- ## Node.js **SendGrid:** ```ts import sgMail from '@sendgrid/mail' sgMail.setApiKey(process.env.SENDGRID_API_KEY!) await sgMail.send({ to: 'user@example.com', from: 'you@yourdomain.com', subject: 'Hello', html: 'It works.
', }) ``` **Ranla:** ```ts import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY!) await client.emails.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Hello', html: 'It works.
', }) ``` --- ## Webhooks SendGrid Event Webhook posts JSON arrays of events. Ranla sends one signed JSON object per delivery with `SuperSendTX-Signature` (HMAC-SHA256). Map event names as follows: | SendGrid event | Ranla | |----------------|--------------| | `processed` | `email.sent` | | `delivered` | `email.delivered` | | `deferred` | `email.delivery_delayed` | | `bounce` | `email.bounced` | | `dropped` | `email.failed` or `email.suppressed` | | `open` | `email.opened` | | `click` | `email.clicked` | | `spamreport` | `email.complained` | Use **Send test event** on the dashboard webhooks page (or `POST /emails/test`) to verify your handler before cutover. --- ## Checklist 1. Create a Ranla API key (`rnl_…`) 2. Add and verify your sending domain (SPF, DKIM, return-path) 3. Swap the client to `api.ranla.ai` and map fields per table above 4. Recreate webhook endpoints and update signature verification 5. Send from Sandbox, then production after domain verify --- ## Compare Marketing overview: [SendGrid alternative](https://supersendtx.com/compare/sendgrid). --- # Password reset email best practices Password reset emails, email verification, and magic links are the highest-stakes transactional mail your product sends. A delayed or filtered message locks someone out of their own account. Send them with `POST /emails` (or the npm SDK) from a [verified domain](/domains). This guide covers a production-ready **password reset email** flow: send examples, copy patterns, security habits, templates, and how to wire auth providers (Supabase, Clerk, Auth.js, Better Auth). For the deeper product architecture (tokens, latency budgets, stream isolation), see the Learn guide [Password reset, verification, and magic links](https://supersendtx.com/learn/authentication-emails). --- ## Recommended flow 1. Create an API key in the dashboard 2. Verify your sending domain ([Domains](/domains)) — prefer a transactional subdomain such as `mail.yourdomain.com` or `tx.yourdomain.com` 3. When your app issues a reset or verify token, call Ranla with the link in `html` and `text` 4. Track delivery, bounce, and complaint events via [webhooks](/webhooks) Treat provider acceptance (`200` + message `id`) as “queued,” not “in the inbox.” Wire webhooks if support needs a delivery timeline. --- ## Password reset email example ```js import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY) export async function sendPasswordResetEmail({ to, resetUrl, expiresInMinutes = 30, }: { to: string resetUrl: string expiresInMinutes?: number }) { return client.emails.send({ from: 'noreply@mail.yourdomain.com', to, subject: 'Reset your password', html: `We received a request to reset your password.
This link expires in ${expiresInMinutes} minutes. If you did not request a reset, you can ignore this email.
`.trim(), text: `We received a request to reset your password.\n\nReset password: ${resetUrl}\n\nThis link expires in ${expiresInMinutes} minutes. If you did not request a reset, you can ignore this email.`, }) } ``` curl: ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "from": "noreply@mail.yourdomain.com", "to": "user@example.com", "subject": "Reset your password", "html": "We received a request to reset your password.
This link expires in 30 minutes. If you did not request a reset, you can ignore this email.
", "text": "We received a request to reset your password.\n\nReset password: https://yourapp.com/reset?token=…\n\nThis link expires in 30 minutes. If you did not request a reset, you can ignore this email." }' ``` ### Copy habits that help inbox placement - Use a specific subject (`Reset your password`) — avoid vague “Action required” - Include a plain-text part every time - Put the reset URL in both the button/link and as visible text for clients that strip HTML - State expiry clearly - Never attach marketing modules or unsubscribe footers to auth mail --- ## Email verification example ```js await client.emails.send({ from: 'noreply@mail.yourdomain.com', to: user.email, subject: 'Verify your email', html: `Confirm your email address:
If you did not create an account, you can ignore this email.
`, text: `Confirm your email address: ${verifyUrl}\n\nIf you did not create an account, you can ignore this email.`, }) ``` --- ## Magic link and OTP examples Magic links are the same send pattern with a shorter-lived URL: ```js await client.emails.send({ from: 'noreply@mail.yourdomain.com', to, subject: 'Your sign-in link', html: `This link expires in 10 minutes.
`, text: `Sign in: ${magicUrl}\n\nThis link expires in 10 minutes.`, }) ``` One-time codes should stay short and readable in plain text (users often read OTP mail on a second device): ```js await client.emails.send({ from: 'noreply@mail.yourdomain.com', to, subject: 'Your verification code', html: `Your code is ${otp}.
It expires in 10 minutes.
`, text: `Your code is ${otp}.\n\nIt expires in 10 minutes.`, }) ``` --- ## Security habits (application side) Ranla delivers the message; your app owns the token. - Generate high-entropy, single-use tokens and store them hashed - Keep expiry short (minutes for magic links / OTP; tens of minutes for password reset) - Return the same response whether or not the account exists (no enumeration) - Prefer first-party HTTPS links — avoid third-party URL shorteners in auth mail - Make sends idempotent so retries do not create three reset emails --- ## Latency and deliverability Auth mail should target **seconds** from user action to inbox. Budget queue time in your workers, not only the HTTP round trip to the API. - Authenticate the sending domain with SPF, DKIM, and DMARC before production traffic ([Domains](/domains)) - Keep auth mail on a transactional identity — do not send campaigns from the same subdomain - Suppress hard bounces immediately so retries do not burn reputation ([Suppressions](/suppressions)) If resets land in spam, diagnose authentication and reputation before rewriting subject lines — see [Why emails go to spam](https://supersendtx.com/learn/why-emails-go-to-spam). --- ## Templates For repeated auth copy, store HTML in [Templates](/templates) and send by alias with variables (`reset_url`, `expires_in`) instead of inlining markup in every service. Keep a plain-text body on the send call or in the template so filters and accessibility stay covered. --- ## Auth providers (Supabase, Clerk, Auth.js, Better Auth) Those products often expect **custom SMTP** or an **email hook**. Ranla supports both: | Provider | Typical path | Guide | |----------|--------------|-------| | Supabase | Auth hook → `POST /emails`, or custom SMTP | [Supabase Auth email](/guides/supabase) | | Clerk | `email.created` webhook → `supersendtx-clerk` | [Clerk email](/guides/clerk) | | Auth.js / NextAuth | `supersendtx-authjs` email provider | [Auth.js email](/guides/authjs) | | Better Auth | `sendResetPassword` / `sendVerificationEmail` | [Better Auth email](/guides/better-auth) | Overview of the shared pattern: [Auth provider email](/guides/auth-provider-email). SMTP relay details: [SMTP](/smtp). --- ## Related - [Auth provider email](/guides/auth-provider-email) - [Emails API](/emails) - [Webhooks](/webhooks) - [Templates](/templates) - [React Email](/guides/react-email) - [Quickstart](/quickstart) - [Transactional email best practices](https://supersendtx.com/learn/transactional-email-best-practices) (Learn) - [Authentication emails](https://supersendtx.com/learn/authentication-emails) (Learn) --- # Auth provider email (password reset, magic links, verification) Many auth stacks let you bring your own email for **password reset**, magic links, verification, and OTP messages. Ranla is built for that traffic over the **HTTP API**, with [SMTP relay](/smtp) when a provider only accepts custom SMTP. > Prefer HTTP hooks or server routes calling `POST /emails` when the provider supports them. Use SMTP credentials from the dashboard when the product only exposes an SMTP form. For standalone send examples and copy patterns, see [Password reset email best practices](/guides/password-reset-emails). --- ## Pattern (all providers) 1. Verify `yourdomain.com` (or a TX subdomain) in Ranla 2. Create an `rnl_...` API key 3. In the auth provider’s **email hook**, **custom mailer**, or your app’s auth callback, send with Ranla 4. Keep `from` on your verified domain 5. Optionally subscribe to [webhooks](/webhooks) for delivery, bounce, and complaint events ```js import { Ranla } from '@supersend/ranla' const tx = new Ranla(process.env.RANLA_API_KEY) export async function sendAuthEmail({ to, subject, html, text, }: { to: string subject: string html: string text?: string }) { return tx.emails.send({ from: 'noreply@mail.yourdomain.com', to, subject, html, text, }) } ``` Use this helper from password-reset, verification, and magic-link paths so every auth message shares one authenticated identity. --- ## Supabase Use the **Send Email** auth hook with an Edge Function that calls `POST /emails`, **or** configure [Supabase custom SMTP](/smtp) with a Ranla SMTP credential (host `smtp.supersendtx.com`, username `supersendtx`). Full hook setup: **[Supabase Auth email](/guides/supabase)**. For local testing before your domain verifies, use the Ranla sandbox sender limited to your account email ([Quickstart](/quickstart)). --- ## Clerk Clerk has no custom SMTP form for auth templates. Turn off **Delivered by Clerk**, listen for `email.created`, and deliver with [`supersendtx-clerk`](https://github.com/Super-Send/supersendtx-clerk): ```ts import { verifyWebhook } from '@clerk/nextjs/webhooks' import { createClerkEmailDeliverer } from 'supersendtx-clerk' const deliver = createClerkEmailDeliverer({ from: 'noreply@mail.yourdomain.com', }) export async function POST(req: Request) { const evt = await verifyWebhook(req) if (evt.type === 'email.created') { await deliver(evt.data) } return new Response('ok') } ``` Full walkthrough: **[Clerk email](/guides/clerk)**. --- ## Auth.js (NextAuth) Use the drop-in **Ranla** email provider for magic links and verification flows: ```ts import NextAuth from 'next-auth' import SuperSendTX from 'supersendtx-authjs' export const { handlers, auth, signIn, signOut } = NextAuth({ adapter: /* database adapter required */, providers: [ SuperSendTX({ from: 'noreply@mail.yourdomain.com' }), ], }) ``` Set `AUTH_SUPERSENDTX_KEY` or `RANLA_API_KEY`. Full walkthrough: **[Auth.js / NextAuth email](/guides/authjs)**. --- ## Better Auth Better Auth uses `sendVerificationEmail` / `sendResetPassword` callbacks — wire those to `POST /emails` (or the npm SDK) with the same `from` domain as your other TX mail. Full walkthrough: **[Better Auth email](/guides/better-auth)**. --- ## Checklist - [ ] Domain verified in Ranla (SPF / DKIM / return-path) - [ ] `from` matches that domain (prefer a transactional subdomain) - [ ] Password reset, verification, and magic-link paths all use the same sender identity - [ ] Secrets only in server env (`RANLA_API_KEY`) - [ ] HTML **and** plain-text bodies on auth sends - [ ] Webhooks optional but recommended for delivery/bounce visibility - [ ] Sandbox used only for self-tests before production cutover --- ## Related - [Password reset email best practices](/guides/password-reset-emails) - [Clerk email](/guides/clerk) - [Auth.js / NextAuth email](/guides/authjs) - [Better Auth email](/guides/better-auth) - [Supabase Auth email](/guides/supabase) - [Emails API](/emails) - [SMTP](/smtp) - [Migration guides](/migration) - [Authentication emails](https://supersendtx.com/learn/authentication-emails) (Learn) --- # Supabase Auth email with Ranla Send Supabase signup, magic link, password reset, and invite emails through Ranla using the **Send Email** auth hook and an Edge Function. > Ranla does **not** require the Send Email hook if your provider supports custom SMTP. Create an SMTP credential in the dashboard and paste host, username `supersendtx`, and password into Supabase → Authentication → SMTP. See [SMTP relay](/smtp). For hook-based delivery (Edge Functions), use the HTTP API (`POST /emails`) from your handler — the same pattern Supabase documents for other ESPs. --- ## When to use this - You built on **Supabase Auth** (often via Lovable, Vite, or Next.js) and want branded mail on your domain - You outgrew Supabase’s built-in SMTP limits or need delivery visibility in Ranla - You want one transactional provider for auth mail **and** product mail (receipts, alerts) --- ## Architecture 1. User triggers auth (signup, reset, magic link, etc.) 2. Supabase Auth calls your **Send Email** hook (HTTPS Edge Function) 3. The function verifies the webhook signature, builds the message, and calls `POST https://api.ranla.ai/emails` 4. Return `200` with `{}` so Supabase knows the email was handled Reference: [Supabase Send Email Hook](https://supabase.com/docs/guides/auth/auth-hooks/send-email-hook). --- ## Prerequisites 1. Ranla account + `rnl_…` API key 2. Verified sending domain (or Sandbox for self-tests only — see [Quickstart](/quickstart)) 3. Supabase project with the [Supabase CLI](https://supabase.com/docs/guides/cli) installed --- ## 1. Create the Edge Function ```bash supabase functions new send-email ``` `supabase/functions/send-email/index.ts`: ```ts import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') ?? '').replace(/^v1,whsec_/, '') const apiKey = Deno.env.get('RANLA_API_KEY') const fromEmail = Deno.env.get('SUPERSENDTX_FROM_EMAIL') ?? 'noreply@yourdomain.com' const projectRef = Deno.env.get('SUPABASE_PROJECT_REF') const subjects: RecordYour verification code is ${email_data.token}
` : `` const text = action === 'reauthentication' ? `Your verification code is ${email_data.token}` : `${subject}: ${link}` const res = await fetch('https://api.ranla.ai/emails', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: fromEmail, to: user.email, subject, html, text, }), }) if (!res.ok) { const body = await res.text() throw new Error(`Ranla ${res.status}: ${body}`) } } catch (error) { console.error(error) return new Response(JSON.stringify({ error: String(error) }), { status: 500, headers: { 'Content-Type': 'application/json' }, }) } return new Response(JSON.stringify({}), { status: 200, headers: { 'Content-Type': 'application/json' }, }) }) ``` Customize subjects and HTML (or render [React Email](/guides/react-email) in the function) for production. --- ## 2. Set secrets and deploy `supabase/functions/.env` (local) — do not commit: ```bash RANLA_API_KEY=rnl_your_key_here SUPERSENDTX_FROM_EMAIL=noreply@yourdomain.com SUPABASE_PROJECT_REF=your-project-ref SEND_EMAIL_HOOK_SECRET=v1,whsec_... # from Supabase dashboard after step 3 ``` ```bash supabase secrets set --env-file supabase/functions/.env supabase functions deploy send-email --no-verify-jwt ``` Note the deployed function URL (e.g. `https://Welcome.
', }) ``` --- ## Related - [Node.js + Nodemailer](/frameworks/node) - [Better Auth](/guides/better-auth) (if Payload auth uses Better Auth) - [Next.js](/frameworks/nextjs) --- # n8n community node Send **Ranla** transactional email from [n8n](https://n8n.io) workflows with the community package [`n8n-nodes-supersendtx`](https://github.com/Super-Send/n8n-nodes-supersendtx). This integrates **Ranla** only (`rnl_` API keys, `api.ranla.ai`). It is not the SuperSend cold-email product. ## Install In n8n: **Settings → Community Nodes → Install** → `n8n-nodes-supersendtx`. Self-hosted alternative: ```bash npm install n8n-nodes-supersendtx ``` ## Credentials 1. Create an API key in the Ranla dashboard (`rnl_…`). 2. In n8n, add **Ranla API** credentials with that key. 3. Leave **API Base URL** as `https://api.ranla.ai` unless you use a custom deployment. ## Send an email 1. Add the **Ranla** node. 2. Resource: **Email** → Operation: **Send**. 3. Set **From** (verified domain), **To**, **Subject**, and **HTML** and/or **Text**. 4. Optional: Reply-To, CC/BCC, idempotency key, schedule, template id, unsubscribe. The node maps to `POST /emails`. See [Send emails](/api/emails). ## Other operations | Operation | Use | |-----------|-----| | Get | Fetch one email by id | | Get Many | List recent sends (limit + cursor) | ## Verification status The package follows n8n’s [community node standards](https://docs.n8n.io/integrations/community-nodes/build-community-nodes/) (no runtime dependencies, MIT, declarative HTTP). After npm publish with provenance, submit it in the [n8n Creator Portal](https://creators.n8n.io/nodes) for in-app discovery. --- # Send React Email with Ranla Use [React Email](https://react.email) to author components, preview them locally, then **send React Email** through the Ranla Node SDK — without tying delivery to a single ESP. The SDK accepts a `react` element and compiles it to HTML before `POST /emails`. You do **not** need to fork React Email or add `@react-email/components` to Ranla — keep authoring in your app. Ranla is **not affiliated** with the React Email project; we are a transactional send API that optionally renders your components at send time. ## About React Email [React Email](https://react.email) is a **separate open-source project** (MIT license) for authoring email UIs with React. Install `@react-email/render` and optional `@react-email/components` in **your** application. Ranla does not bundle React Email — the SDK optionally calls `@react-email/render` at send time to produce HTML for our API. Author and preview with React Email’s own CLI; delivery is through Ranla. --- ## Prerequisites - A Ranla API key (`rnl_…`) - A verified sending domain (or sandbox: `from` = `noreply@mail.supersendtx.com`, `to` = your account email) - Node 18+ --- ## 1. Install ```bash npm install @supersend/ranla @react-email/render react # Optional — components + local preview tooling from React Email npm install @react-email/components npx create-email@latest ``` `@react-email/render` is an **optional peer** of `supersendtx`. Install it only if you use the `react` option. Without it, send with `html` / `text` / `template` as usual. --- ## 2. Author and preview Create a component (example): ```tsx // emails/WelcomeEmail.tsx import * as React from 'react' import { Html, Button, Text } from '@react-email/components' export function WelcomeEmail({ name }: { name: string }) { return (Hi Ada
', }) ``` Or use a published [dashboard template](/templates) with `template: { id | alias, variables }`. --- ## Troubleshooting | Symptom | Fix | |---------|-----| | Error about `@react-email/render` | `npm install @react-email/render react` | | `403` unverified domain | Verify DNS or use sandbox `from` / account `to` | | Want MCP / agents | [MCP server](/ai/mcp) · [Agent Skills](/ai/agent-skills) — agents should link React Email’s own skills for authoring | --- ## FAQ ### Can I use React Email without Resend? Yes. React Email is an open-source authoring toolchain. Ranla’s Node SDK accepts `react` the same way many teams expect from a modern send API: install `supersendtx` + `@react-email/render`, pass your component to `emails.send({ react })`, and we deliver HTML over our transactional infrastructure. Preview still uses React Email’s CLI. ### Is `react` the same as dashboard templates? No. `react` renders in **your** Node process at send time (or you push rendered HTML with `supersendtx templates push`). Dashboard templates are stored aliases with variables for non-engineers and multi-language SDKs. Many teams use React Email in git for critical flows and aliases for stable receipts. ### Why did send fail with a peer dependency error? The `react` option requires `@react-email/render` (and `react`) installed in the app. Without that peer, use `html`, `text`, or `template` instead — or run `npm install @react-email/render react`. --- ## Related - [Templates API](/templates) — server-side aliases and variables - [Password reset emails](/guides/password-reset-emails) - [Authentication emails (Learn)](https://supersendtx.com/learn/authentication-emails) - [Transactional email best practices (Learn)](https://supersendtx.com/learn/transactional-email-best-practices) - [Node.js](/frameworks/node) · [Next.js](/frameworks/nextjs) - React Email docs: [react.email](https://react.email) --- # AI app builders Use Ranla in Lovable, Replit, Bolt, Base44, v0, and similar AI builders. **Two kinds of install surface:** 1. **Agent tools (MCP)** — the builder’s AI can call Ranla while you develop (Replit one-click MCP; Cursor/Claude via [MCP](../ai/mcp.md)). 2. **App runtime (Secrets + code)** — your deployed app sends mail via `RANLA_API_KEY` and `POST /emails` (or [SMTP](../api/smtp.md)). Most guides below cover (2). Replit also has a one-click path for (1). ## Pick your builder | Builder | Best for | Install surface | |---------|----------|-----------------| | [Lovable](/builders/lovable) | React + Supabase apps, chat-driven features | Secrets + agent prompt (HTTP or SMTP) | | [Replit](/builders/replit) | Full-stack Replit Agent projects | MCP badge + Secrets + agent prompt | | [Bolt.new](/builders/bolt) | Vite / React prototypes from chat | `.env` + agent prompt | | [Base44](/builders/base44) | Base44 apps with optional custom email | When to use TX vs built-in mail | | [v0](/builders/v0) | Next.js from Vercel v0 | Route Handler + Vercel env | ## Before you paste a prompt 1. Create a [Ranla account](https://app.ranla.ai) and an API key (`rnl_…`). 2. Store it as **`RANLA_API_KEY`** in the builder’s secrets / env UI (server-side only). 3. For a first send before your domain verifies, use the sandbox rules in [Quickstart](../quickstart.md) and [Agent skill notes](../ai/agent-skill.md). ## Standard env var ```bash RANLA_API_KEY=rnl_your_key_here ``` Some builders also accept `STX_API_KEY` — if their UI suggests that name, map it to the same `rnl_…` value. Prefer `RANLA_API_KEY` in generated code for consistency with our SDK and docs. ## Reference - [Agent skill notes](../ai/agent-skill.md) — integration defaults for AI agents - [OpenAPI](../openapi.yaml) — API contract - [Next.js](../frameworks/nextjs.md) — server-side send pattern - [Auth provider email](../guides/auth-provider-email.md) — Clerk / Auth.js hooks - [Supabase Auth email](../guides/supabase.md) — Send Email hook + Edge Function --- # Ranla + Lovable Lovable’s install surface is **Secrets + a chat prompt**: store your `rnl_` key, paste the prompt, and let Lovable wire server-side sends (often via Supabase Auth). There is no separate Lovable “connector package.” The agent writes the Edge Function / hook against `POST /emails` (or [SMTP](../api/smtp.md) if you prefer Supabase’s custom SMTP UI). ## When to use Ranla - Password reset, verification, receipts, and other **transactional** mail - Branded `from` on your own domain after DNS verify - Supabase Auth via a Send Email hook / Edge Function, or [custom SMTP](../api/smtp.md) with a Ranla credential Lovable projects often use Supabase — see [Supabase Auth email](../guides/supabase.md) for the hook path and the SMTP alternative. ## 1. Add your API key (Secrets) 1. Open [app.ranla.ai](https://app.ranla.ai) → **API Keys** → create a key (`rnl_…`). 2. In Lovable, open your project **Settings** → **Secrets** (or the env/secrets panel Lovable exposes for backend code). 3. Add: ```text RANLA_API_KEY=rnl_your_key_here ``` Use the secret name exactly — server routes and Edge Functions should read `process.env.RANLA_API_KEY`. Never put the key in client-side React components. ## 2. Paste this prompt in Lovable chat ```text Integrate Ranla transactional email into this app. Docs (read first): - https://docs.ranla.ai/guides/supabase - https://docs.ranla.ai/ai/agent-skill - https://docs.ranla.ai/openapi.yaml - https://docs.ranla.ai/builders/lovable - https://docs.ranla.ai/smtp Rules: 1. Store the API key in RANLA_API_KEY (already in Lovable Secrets). Never expose rnl_ keys in the browser. 2. Prefer POST https://api.ranla.ai/emails with Authorization: Bearer $RANLA_API_KEY (Supabase Edge Function or server route). 3. Alternative: configure Supabase custom SMTP with Ranla (host smtp.supersendtx.com, username supersendtx, password from dashboard SMTP credentials). 4. Until domain verification and Free production unlock (both required: verified domain + payment method on file), sandbox sends must use from "noreply@mail.supersendtx.com" and to only our account email. 5. Add a server-side send path for password-reset / verification email. 6. Use canonical fields: html, text, reply_to. Prefer npm package supersendtx if the backend is Node. After domain verify + payment method on file (Free production, no charge until upgrade), switch from to noreply@ourdomain.com. ``` Replace “our account email” with the email on your Ranla account if the agent asks. ## 3. Minimal server send (Node / Edge) ```ts const res = await fetch('https://api.ranla.ai/emails', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RANLA_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ from: 'noreply@yourdomain.com', to: 'user@example.com', subject: 'Verify your email', html: 'Click the link to verify.
', }), }) ``` Or install `supersendtx` and use the SDK — see [Next.js](../frameworks/nextjs.md). ## Sandbox self-test Send to your Ranla account email only, with `from: "noreply@mail.supersendtx.com"`, until your domain is verified and Free production is unlocked (payment method on file): ```json { "from": "noreply@mail.supersendtx.com", "to": "YOUR_ACCOUNT_EMAIL", "subject": "Ranla sandbox check", "html": "It works.
" } ``` ## Production 1. Add your domain in the Ranla dashboard. 2. Apply DNS (Cloudflare integration or registrar credentials). 3. Verify, add a payment method to unlock Free production (no charge until you upgrade), then update `from` to an address on that domain. ## Optional: MCP while you build If you use Cursor (or another MCP client) alongside Lovable, connect hosted MCP at `https://mcp.ranla.ai/mcp` with OAuth — see [MCP server](../ai/mcp.md). That helps the coding agent; your Lovable app still needs Secrets (or SMTP) for runtime sends. ## Links - [Quickstart](../quickstart.md) - [Domains API](../api/domains.md) - [SMTP relay](../api/smtp.md) - [MCP server](../ai/mcp.md) - [Password reset guide](../guides/password-reset-emails.md) - [Supabase Auth email](../guides/supabase.md) --- # Ranla + Replit Two install surfaces, depending on the job: | Surface | Use when | |---------|----------| | **MCP (Agent tools)** | Replit Agent should call Ranla (send, domains, templates) while building | | **Secrets + app code** | Your running repl sends mail (password reset, invites, notifications) | Most projects use both: MCP while building, Secrets for production paths in the app. ## Add to Replit (MCP) One-click install of the hosted MCP server (`https://mcp.ranla.ai/mcp`). Replit uses OAuth — no API key in the link. [](https://replit.com/integrations?mcp=eyJkaXNwbGF5TmFtZSI6IlN1cGVyU2VuZCBUWCIsImJhc2VVcmwiOiJodHRwczovL21jcC5zdXBlcnNlbmR0eC5jb20vbWNwIn0=) Or open: [Add Ranla MCP](https://replit.com/integrations?mcp=eyJkaXNwbGF5TmFtZSI6IlN1cGVyU2VuZCBUWCIsImJhc2VVcmwiOiJodHRwczovL21jcC5zdXBlcnNlbmR0eC5jb20vbWNwIn0=) After install, authorize with your Ranla account when prompted. Full MCP docs: [MCP server](../ai/mcp.md). ## Wire email into your app (Secrets) ### 1. Add your API key 1. Create an API key at [app.ranla.ai](https://app.ranla.ai) (`rnl_…`). 2. In Replit, open **Tools** → **Secrets** (or **Integrations** → secrets, depending on Replit UI). 3. Create a secret: | Key | Value | |-----|-------| | `RANLA_API_KEY` | `rnl_your_key_here` | Replit injects secrets as environment variables. Access via `process.env.RANLA_API_KEY` (Node) or `os.environ["RANLA_API_KEY"]` (Python). Never expose the key to the client. ### 2. Paste this prompt in Replit Agent ```text Integrate Ranla transactional email into this Replit project. Docs: - https://docs.ranla.ai/ai/agent-skill - https://docs.ranla.ai/openapi.yaml - https://docs.ranla.ai/builders/replit - https://docs.ranla.ai/ai/mcp Rules: 1. Read RANLA_API_KEY from Replit Secrets (already configured). Never log or expose the key to the client. 2. Send with POST https://api.ranla.ai/emails and Authorization: BearerThanks for your order.
" } ``` ## Production checklist - [ ] Domain added and verified in Ranla - [ ] `from` uses verified domain - [ ] API key only in secrets, not in frontend - [ ] Bounce/complaint handling via [webhooks](../api/webhooks.md) if needed ## Links - [Quickstart](../quickstart.md) - [Domains](../api/domains.md) - [Auth provider email](../guides/auth-provider-email.md) --- # Ranla + v0 (Vercel) v0 generates Next.js apps. Run Ranla from a **Route Handler or Server Action** and store `RANLA_API_KEY` in Vercel environment variables. ## When to use Ranla - v0 apps deployed to Vercel that need password reset, magic links, or receipts - Server Components / Route Handlers — same pattern as [Next.js](../frameworks/nextjs.md) - Production sends from your verified domain ## 1. Add your API key **Local** — `.env.local`: ```bash RANLA_API_KEY=rnl_your_key_here ``` **Vercel** — Project → **Settings** → **Environment Variables**: | Name | Environments | |------|----------------| | `RANLA_API_KEY` | Production, Preview, Development | Redeploy after adding the variable. ## 2. Paste this prompt in v0 chat ```text Integrate Ranla transactional email into this Next.js app. Docs: - https://docs.ranla.ai/ai/agent-skill - https://docs.ranla.ai/frameworks/nextjs - https://docs.ranla.ai/openapi.yaml - https://docs.ranla.ai/builders/v0 Rules: 1. RANLA_API_KEY is server-only (process.env) — never use NEXT_PUBLIC_ for the API key. 2. Send via POST https://api.ranla.ai/emails or npm package supersendtx from a Route Handler / Server Action. 3. Sandbox: from "noreply@mail.supersendtx.com", to account email only until domain verify. 4. Add app/api/... route or server action for at least one transactional email flow. 5. Use html, text, reply_to. Follow Next.js server patterns — no client-side rnl_ keys. Document that Vercel env var RANLA_API_KEY must be set before deploy. ``` ## 3. Route Handler example Create `app/api/email/send/route.ts`: ```ts import { NextResponse } from 'next/server' import { Ranla } from '@supersend/ranla' const tx = new Ranla(process.env.RANLA_API_KEY!) export async function POST(request: Request) { const body = await request.json() const result = await tx.emails.send({ from: body.from ?? 'noreply@yourdomain.com', to: body.to, subject: body.subject, html: body.html, text: body.text, }) return NextResponse.json(result) } ``` Install: `npm install @supersend/ranla` ## Sandbox Until your domain verifies, test with the sandbox sender and your Ranla account email — see [Agent skill notes](../ai/agent-skill.md). ## Production 1. Verify domain in Ranla dashboard. 2. Set production `from` to your domain. 3. Confirm `RANLA_API_KEY` is set on Vercel Production. ## Links - [Next.js guide](../frameworks/nextjs.md) - [Quickstart](../quickstart.md) - [OpenAPI](../openapi.yaml) --- # Emails Send transactional email and list recent sends. Migrating from Resend? See [`docs/migration.md`](../migration.md). --- ## Send email `POST /emails` Send a transactional email from a verified domain. Base URL: `https://api.ranla.ai` --- ## Authentication ```http Authorization: Bearer rnl_… ``` API keys are created in the dashboard (**API Keys**). Keys start with `rnl_`. --- ## Request body | Field | Type | Required | Description | |-------|------|----------|-------------| | `from` | `string` or `{ email, name? }` | Yes | Must be on a verified domain | | `to` | `string` or `string[]` | Yes | Recipient(s) | | `subject` | `string` | Yes | Subject line | | `html` | `string` | No* | HTML body (`htmlBody` / `html_body` aliases accepted) | | `text` | `string` | No* | Plain-text body (`textBody`, `text_body`, and `plain_body` aliases accepted) | | `reply_to` | `string` or `string[]` | No | Reply-to address(es) (`replyTo` alias accepted) | | `cc` | `string` or `string[]` | No | CC recipient(s) | | `bcc` | `string` or `string[]` | No | BCC recipient(s) | | `tags` | `{ name, value }[]` or object | No | Up to 10 key/value tags stored with the email | | `headers` | `object` | No | Custom headers; dangerous transport headers are rejected | | `attachments` | `array` | No | Up to 10 files, 10MB decoded each; executable/script extensions are blocked. Attachment aliases `filename`, `contentType`, `content`, and `contentId` are also accepted. | | `scheduled_at` | `string` | No | ISO 8601 timestamp. Creates a scheduled email instead of sending immediately (`scheduledAt` alias accepted) | | `unsubscribe` | `boolean` | No | Opt in to managed unsubscribe (see below). Off by default for transactional mail. | | `category` | `string` | No | Optional override: `transactional`, `product`, or `newsletter`. Inherits from template when sending with a template. | \* At least one of `html` or `text` is required. ### Managed unsubscribe (opt-in) Pure transactional mail (password resets, receipts, security alerts) should leave `unsubscribe` off. When `unsubscribe: true`, Ranla: - Injects RFC 8058 `List-Unsubscribe` and `List-Unsubscribe-Post` headers (one-click unsubscribe) - Replaces `{{unsubscribe_url}}` in `subject`, `html`, and `text` with a signed confirmation link - Binds the link to the first `to` address for that message When sending with a published template, the template's `unsubscribe_enabled` flag is used unless you pass `unsubscribe` explicitly on the send request. Product and newsletter templates auto-enable managed unsubscribe and support category-scoped opt-out — recipients can unsubscribe from that category without blocking transactional mail. ### Template categories Templates have a `category` that controls preference management: | Category | Unsubscribe behavior | |----------|---------------------| | `transactional` | Default. No category opt-out. Global suppression only. | | `product` | Category-scoped opt-out (onboarding tips, upgrade nudges) | | `newsletter` | Same mechanism as product, for broadcast-style mail | Sends to recipients who opted out of a category return `422`: ```json { "error": { "message": "One or more recipients opted out of this email category", "code": "validation_error", "details": { "category_unsubscribed": ["user@example.com"], "category": "product", "email_id": "msg_…" } } } ``` Global suppressions return the same status with `details.suppressed` instead. ```json { "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Product updates", "html": "Hi there.
", "unsubscribe": true } ``` ### Example ```json { "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello from Ranla", "html": "It works.
" } ``` With display name: ```json { "from": { "email": "you@yourdomain.com", "name": "Your App" }, "to": ["user@example.com", "other@example.com"], "subject": "Hello", "html": "Hi
", "text": "Hi", "reply_to": ["support@yourdomain.com"], "tags": [{ "name": "order_id", "value": "ord_123" }], "headers": { "X-Entity-ID": "ord_123" } } ``` ### Sandbox self-test Before your own domain is verified, you can self-test with the shared sandbox domain: ```json { "from": "noreply@mail.supersendtx.com", "to": "owner@example.com", "subject": "Sandbox check", "html": "Sandbox works.
" } ``` The sandbox sender is limited to the account email attached to the API key owner. After your domain is verified, you can also self-test **from your verified domain** on the free plan — still limited to the account email: ```json { "from": "you@yourdomain.com", "to": "owner@example.com", "subject": "Verified domain check", "html": "My domain works.
" } ``` Add a payment method to unlock Free production (send to other recipients on your verified domain — no charge until you upgrade). Upgrade to Pro or Scale for more volume. --- ## Responses ### 200 — Accepted ```json { "id": "msg_abc123…", "status": "sent" } ``` Scheduled sends return: ```json { "id": "msg_abc123…", "status": "scheduled" } ``` `id` is a stable public message ID (`msg_…`). Use it to correlate webhook events and dashboard activity. --- ## Idempotency Optional header on `POST /emails`: ```http Idempotency-Key: order-123 ``` Keys are scoped per account, retained for **24 hours**. Replays return the original response. Reusing a key with a different body returns **409** `idempotency_conflict`. ```ts await client.emails.send({ …, idempotencyKey: 'order-123' }) ``` --- ## Retrieve email `GET /emails/{id}` Returns one send by public id (`msg_…`). ```ts const { email } = await client.emails.get('msg_abc123…') ``` --- ## Resend `POST /emails/{id}/resend` Creates a **new** send from a previously stored email (same from/to/subject/body, tags, and headers). Returns a new `msg_…` id. Attachment bytes are not stored, so attachments are omitted. Dashboard: Emails → **Resend** on any row. SDK: ```ts await client.emails.resend('msg_abc123…') ``` CLI: ```bash supersendtx emails resend --id msg_abc123… ``` --- ## Schedule, reschedule, and cancel Add `scheduled_at` to `POST /emails` to store the message and enqueue it for later delivery: ```json { "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Tomorrow", "html": "See you tomorrow.
", "scheduled_at": "2026-08-01T12:00:00.000Z" } ``` Scheduled emails can be updated only while their status is `scheduled`: ```http PATCH /emails/msg_abc123… ``` ```json { "scheduled_at": "2026-08-01T13:00:00.000Z" } ``` Cancel a scheduled email: ```json { "cancel": true } ``` SDK: ```ts await client.emails.update('msg_abc123…', { scheduledAt: '2026-08-01T13:00:00.000Z' }) await client.emails.cancel('msg_abc123…') ``` CLI: ```bash supersendtx emails cancel --id msg_abc123… ``` Scheduled sends store message bodies so the worker can send them later. Attachments are not supported on scheduled emails in v1 because raw attachment data is not stored. --- ## Batch send `POST /emails/batch` Send up to 100 emails in one request: ```json { "emails": [ { "from": "you@yourdomain.com", "to": "user@example.com", "subject": "Hello", "html": "Hi
" } ] } ``` The response is index-aligned: ```json { "data": [ { "index": 0, "id": "msg_abc123…", "status": "sent" } ] } ``` Per-email validation or delivery failures are returned in that item’s `error`. Attachments are rejected for batch sends in v1. SDK: ```ts const result = await client.emails.batch([{ from, to, subject, html }]) ``` --- ## List emails `GET /emails?limit=25&cursor=…` Returns recent sends for the authenticated API key’s account. `limit` is 1–100 (default 25). Use opaque `cursor` / `next_cursor` for pagination. Responses include rate-limit headers: `ratelimit-limit`, `ratelimit-remaining`, `ratelimit-reset`. ### 200 ```json { "emails": [ { "id": "msg_abc123…", "from": "you@yourdomain.com", "to": ["user@example.com"], "cc": [], "bcc": [], "reply_to": [], "subject": "Hello", "status": "delivered", "last_event": "delivered", "bounce_reason": null, "tags": [], "scheduled_at": null, "cancelled_at": null, "created_at": "2026-07-25T12:00:00.000Z", "sent_at": "2026-07-25T12:00:01.000Z", "delivered_at": "2026-07-25T12:00:05.000Z", "bounced_at": null } ], "has_more": false, "next_cursor": null } ``` Status values: `queued`, `scheduled`, `sent`, `delivered`, `bounced`, `failed`, `cancelled`. ### SDK ```ts const { emails, next_cursor } = await client.emails.list({ limit: 10 }) ``` ### Error format All errors return: ```json { "error": { "message": "Human-readable message", "code": "validation_error", "details": {} } } ``` | Status | When | |--------|------| | 400 | Invalid JSON, missing fields, invalid `from` | | 401 | Missing/invalid Bearer token or API key | | 403 | `from` domain not verified, sandbox/verified-domain sender used for recipients outside the account email, or send to other recipients without Free production unlocked (payment method on file) | | 409 | Idempotency key conflict | | 429 | Free plan send limit (`plan_limit`) | | 502 | Upstream mail delivery error | | 503 | Mail delivery temporarily unavailable | ### Attachment limits - Maximum 10 attachments per non-batch, non-scheduled send - Maximum 10MB decoded size per attachment - Blocked filename extensions: `.exe`, `.bat`, `.cmd`, `.com`, `.scr`, `.js`, `.vbs`, `.dll` --- ## Examples ### curl ```bash curl -X POST https://api.ranla.ai/emails \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"from":"you@yourdomain.com","to":"user@example.com","subject":"Hello","html":"Hi
"}' ``` ### Node.js ```ts import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY!) await client.emails.send({ from: 'you@yourdomain.com', to: 'user@example.com', subject: 'Hello', html: 'Hi
', }) ``` Throws `SuperSendTXError` on failure (`error.status`, `error.message`, `error.details`). ### CLI ```bash supersendtx emails send \ --from you@yourdomain.com \ --to user@example.com \ --subject "Hello" \ --html "Hi
" \ --tag order_id=ord_123 \ --schedule 2026-08-01T12:00:00.000Z ``` Requires `RANLA_API_KEY` or `--api-key`. --- ## OpenAPI Machine-readable spec: [`openapi/supersendtx.yaml`](../../openapi/supersendtx.yaml) --- # Domains API Manage sending domains, DNS verification, inbound receiving flags, tracking preferences, TLS mode, one-click DNS apply, and DMARC/BIMI guidance. Base URL: `https://api.ranla.ai` Auth: ```http Authorization: Bearer rnl_... ``` --- ## Create a domain `POST /domains` ```bash curl -X POST https://api.ranla.ai/domains \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "example.com" }' ``` Success response: ```json { "domain": { "id": "d_123", "name": "example.com", "status": "pending", "created_at": "2026-07-26T00:00:00.000Z", "verified_at": null, "open_tracking": false, "click_tracking": false, "tls_mode": "opportunistic" }, "records": [ { "type": "TXT", "host": "_supersendtx.example.com", "value": "supersendtx-verify=...", "purpose": "Domain ownership" } ] } ``` To request inbound receiving during creation, include `"inbound_enabled": true`. The response will also include `inbound_records` (MX) and `inbound_note`. Full receiving flow: [`docs/api/inbound.md`](./inbound.md). Expected DNS records (after create syncs with the mail server): - ownership TXT: `_supersendtx.example.com` - DKIM TXT: selector host such as `ss-abc123._domainkey.example.com` with the real public key - return-path CNAME: `rp.example.com` → `rp.supersendtx.com` (DNS only / not proxied) - SPF include: `include:spf.supersendtx.com` - DMARC starter record Ranla registers the domain on the transactional mail server at create time and replaces placeholder DKIM with the real signing records before apply/verify. ### Cross-team claim conflict If the domain is already attached to another Ranla team, the API returns **409** with claim instructions: ```json { "error": { "message": "Domain already belongs to another Ranla team. Remove it from the current team or contact support to claim it.", "code": "conflict", "details": { "reason": "domain_claim_required", "domain": "example.com", "instructions": [ "Ask the current Ranla team admin to remove the domain if you still have access.", "If you own the domain but no longer control the original team, contact support and include proof of DNS control." ] } } } ``` --- ## Get a domain `GET /domains/{id}` Returns the domain, required DNS records, inbound MX guidance, and best-effort DMARC/BIMI analysis: ```json { "domain": { "id": "d_123", "name": "example.com", "status": "verified", "created_at": "2026-07-26T00:00:00.000Z", "verified_at": "2026-07-26T00:05:00.000Z", "open_tracking": true, "click_tracking": false, "tls_mode": "enforced", "inbound_enabled": true, "inbound_status": "active", "inbound_error": null }, "records": [], "inbound_records": [ { "type": "MX", "host": "example.com", "priority": 10, "value": "mx.supersendtx.com", "purpose": "Inbound receiving" } ], "inbound_note": "Inbound receiving requires pointing this hostname's MX record to Ranla. That conflicts with any existing mailbox provider on the same hostname, so use a dedicated subdomain such as inbound.example.com when Google Workspace or Microsoft 365 should keep handling your main mailboxes.", "analysis": { "dmarc": { "host": "_dmarc.example.com", "configured": true, "record": "v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com", "policy": "quarantine", "rua": ["mailto:dmarc@example.com"], "guidance": "DMARC is enforced. Keep report mailboxes monitored and tighten alignment as you roll out BIMI." }, "bimi": { "host": "default._bimi.example.com", "configured": false, "record": null, "location": null, "authority": null, "requires_dmarc_enforcement": false, "guidance": "Optional: add a BIMI record at default._bimi once your branded SVG logo and optional VMC are ready." } } } ``` ### DMARC/BIMI notes - **DMARC** is recommended for every sending domain. - **BIMI** is optional, but most mailbox providers expect DMARC enforcement (`p=quarantine` or `p=reject`) before BIMI is effective. - `analysis` is advisory. It does not block sending, and it does not replace your own deliverability monitoring. --- ## Verify DNS `POST /domains/{id}` ```json { "action": "verify" } ``` When ownership, SPF, DKIM, and the return-path CNAME are visible in public DNS, Ranla asks the mail server to re-check delivery DNS and marks the domain verified only when mail-server **DKIM** and **return-path** statuses are OK. That step activates customer-domain DKIM signing (`d=yourdomain`) so DMARC can pass. Without DKIM OK, mail would fall back to the shared pool signer. DMARC is reported back but does not currently block verification. Re-running verify on an already-verified domain re-checks mail-server DKIM (useful after DNS propagation). Success responses include: ```json { "verified": true, "signing_ready": true, "mail_server_signing": { "dkim_ok": true, "return_path_ok": true, "dkim_status": "OK", "return_path_status": "OK" } } ``` If public DNS looks correct but the mail server has not yet accepted the DKIM TXT or return-path CNAME, verify returns **400** with `mail_server_signing.dkim_ok` / `return_path_ok` reflecting the failure — wait for propagation and verify again. --- ## Apply DNS `POST /domains/{id}` ### Cloudflare via stored dashboard token ```json { "action": "apply", "provider": "cloudflare" } ``` ### Cloudflare via inline credentials ```json { "action": "apply", "provider": "cloudflare", "credentials": { "apiToken": "cf_...", "zoneId": "..." } } ``` ### GoDaddy via one-time credentials ```json { "action": "apply", "provider": "godaddy", "credentials": { "apiKey": "gd_key", "apiSecret": "gd_secret" } } ``` You can also save GoDaddy credentials under **Settings → Integrations** and omit `credentials` on apply. ### Vercel via stored dashboard token ```json { "action": "apply", "provider": "vercel" } ``` The domain must use **Vercel nameservers**. For team-scoped domains, connect Vercel under **Settings → Integrations** with an optional team id, or pass `credentials.teamId` / `credentials.vercelTeamId` inline. ### Vercel via inline credentials ```json { "action": "apply", "provider": "vercel", "credentials": { "apiToken": "vercel_...", "teamId": "team_..." } } ``` Response shape: ```json { "provider": "godaddy", "domain": "example.com", "results": [ { "purpose": "SPF", "host": "example.com", "action": "merged", "detail": "v=spf1 include:spf.supersendtx.com ~all" } ] } ``` --- ## Update tracking, inbound, and TLS preferences `PATCH /domains/{id}` ```bash curl -X PATCH https://api.ranla.ai/domains/example.com \ -H "Authorization: Bearer $RANLA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inbound_enabled": true, "open_tracking": true, "click_tracking": true, "tls_mode": "enforced" }' ``` Aliases `openTracking`, `clickTracking`, `tlsMode`, and `inboundEnabled` are also accepted. ### Inbound enablement - `inbound_enabled: true` marks the domain for receiving. - Before verification, inbound stays `pending`. - After verification, Ranla activates inbound receiving and updates `inbound_status` to `active` or `error`. - See [`docs/api/inbound.md`](./inbound.md) for received-email retrieval and `email.received` webhooks. ### TLS modes - `opportunistic` — use TLS when the downstream mail path supports it. - `enforced` — require TLS for the downstream mail path. ### Tracking and TLS preferences `open_tracking`, `click_tracking`, and `tls_mode` are stored on the domain immediately when you update them. When open or click tracking is enabled: 1. Ranla provisions a tracking domain for your hostname. 2. The domain detail response includes a **tracking CNAME** (`ps.yourdomain.com` → `track.supersendtx.com`). 3. Apply and verify DNS so the mail server can rewrite links and inject open pixels. 4. `tracking_dns.ok` in `GET /domains/{id}` reports whether the CNAME is live. If delivery-side provisioning fails, settings are still saved but `tracking_error` or `tls_error` may be returned on `PATCH` with a customer-facing message (not raw infrastructure errors). --- ## SDK ```ts import { Ranla } from '@supersend/ranla' const client = new Ranla(process.env.RANLA_API_KEY!) await client.domains.create({ name: 'example.com' }) await client.domains.apply('example.com') await client.domains.apply('example.com', { provider: 'vercel' }) await client.domains.apply('example.com', { provider: 'godaddy', credentials: { apiKey: process.env.GODADDY_API_KEY!, apiSecret: process.env.GODADDY_API_SECRET!, }, }) await client.domains.verify('example.com') const detail = await client.domains.get('example.com') const updated = await client.domains.update('example.com', { open_tracking: true, tls_mode: 'enforced', }) ``` --- ## CLI ```bash supersendtx domains add example.com supersendtx domains apply example.com --provider cloudflare supersendtx domains apply example.com --provider vercel GODADDY_API_KEY=... GODADDY_API_SECRET=... supersendtx domains apply example.com --provider godaddy supersendtx domains verify example.com ``` --- ## Delete a domain `DELETE /domains/{id}` Removes the domain from your account and revokes sending authorization for that hostname. ```bash supersendtx domains delete example.com ``` If upstream teardown fails, the domain is kept and the API returns **502**. --- ## Common errors | Status | Meaning | |--------|---------| | 400 | invalid domain, DNS not ready, missing provider credentials | | 401 | missing or invalid auth | | 403 | domain limit reached | | 404 | domain not found | | 409 | domain already exists or is already claimed | | 502 | Domain registration, teardown, or DNS provider API failed | Full contract: [`openapi/supersendtx.yaml`](../../openapi/supersendtx.yaml) --- # Webhooks Receive signed HTTP callbacks for email lifecycle events. Base URL: `https://api.ranla.ai` Manage endpoints in the dashboard (**Webhooks**) or via the API / SDK / CLI below. --- ## Authentication ```http Authorization: Bearer rnl_… ``` Dashboard session cookies also work for browser requests to `/api/webhooks`. --- ## Supported events | Event | When fired | |-------|------------| | `email.received` | An inbound email arrived on a receive-enabled domain; retrieve full content from `/received-emails/{id}` | | `email.sent` | Ranla accepted the message for delivery | | `email.delivered` | The message was delivered to the recipient mailbox provider | | `email.delivery_delayed` | Delivery was delayed or temporarily held | | `email.bounced` | Hard/soft bounce; recipient is auto-suppressed | | `email.complained` | Recipient marked the message as spam/complaint | | `email.opened` | Open pixel loaded when open tracking is on | | `email.clicked` | Tracked link clicked when click tracking is on | | `email.failed` | Delivery failed; permanent SMTP failures also auto-suppress the recipient | | `email.suppressed` | Send blocked because a recipient is on the suppression list | | `email.scheduled` | Email accepted with `scheduled_at` | | `contact.unsubscribed` | Recipient completed a managed unsubscribe. `data.scope` is `all` (global suppression) or `category` (product/newsletter opt-out only) | | `automation.started` | An active automation matched an incoming event and a run was created | | `automation.step_completed` | One automation step completed successfully | | `automation.completed` | A run finished all steps (or ended early on a false condition) | | `automation.failed` | A run failed (send failure, timeout, validation, etc.) | Open/click tracking are domain settings (`open_tracking` / `click_tracking`) configured per domain. --- ## Create endpoint `POST /webhooks` ```json { "url": "https://yourapp.com/webhooks/supersendtx", "events": ["email.delivered", "email.bounced"] } ``` `events` is optional — defaults to all supported events. ### 200 — Created ```json { "webhook": { "id": "clx…", "url": "https://yourapp.com/webhooks/supersendtx", "events": ["email.delivered", "email.bounced"], "enabled": true, "created_at": "2026-07-25T12:00:00.000Z", "updated_at": "2026-07-25T12:00:00.000Z" }, "secret": "whsec_…" } ``` Copy `secret` immediately — it is only returned once. Use it to verify incoming webhook signatures. --- ## List endpoints `GET /webhooks` Cursor pagination: `limit`, `cursor` → `{ webhooks, has_more, next_cursor }`. --- ## Retrieve endpoint `GET /webhooks/{id}` ```json { "webhook": { "id": "clx…", "url": "https://yourapp.com/webhooks/supersendtx", "events": ["email.delivered", "email.bounced"], "enabled": true, "created_at": "2026-07-25T12:00:00.000Z", "updated_at": "2026-07-25T12:00:00.000Z" } } ``` --- ## Update endpoint `PATCH /webhooks/{id}` ```json { "url": "https://yourapp.com/new-path", "events": ["email.bounced"], "enabled": false } ``` All fields are optional. --- ## Delete endpoint `DELETE /webhooks/{id}` ```json { "ok": true } ``` --- ## Test sink `POST /emails/test` (full-scope API key) ```json { "event": "email.bounced", "email_id": "msg_…", "deliver": true } ``` Builds a webhook payload (optionally for an existing email) and, when `deliver` is not `false`, enqueues delivery to your subscribed endpoints. Useful in CI. --- ## Incoming webhook payload Ranla `POST`s JSON to your endpoint URL when a subscribed event occurs. ### Email event example ```json { "type": "email.received", "created_at": "2026-07-25T12:05:00.000Z", "data": { "email_id": "inb_abc123…", "to": ["sales@inbound.example.com"], "from": "Sender