# Smartletter API — full reference Base URL: https://api.smartletter.ai. OpenAPI: https://api.smartletter.ai/openapi.json. ## Introduction The Smartletter API does what the dashboard does: write and send newsletter issues, manage contacts, collect signups from your own site, read stats, and send transactional email. The Smartletter MCP server offers the same actions to AI agents. Base URL: `https://api.smartletter.ai`. Every request and answer is JSON, except image uploads and the CSV export. ### Quick start - Create an API key in Settings → API keys. - Create a draft, review it, then send it. ```bash curl -X POST https://api.smartletter.ai/newsletters \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"This week in AI","markdown":"# Hello\n\nThree tools this week…"}' curl -X POST https://api.smartletter.ai/newsletters//send \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" -d '{}' ``` ### Machine-readable - OpenAPI 3.1: [https://api.smartletter.ai/openapi.json](https://api.smartletter.ai/openapi.json) - For LLMs: [https://smartletter.ai/llms.txt](https://smartletter.ai/llms.txt) and [https://smartletter.ai/llms-full.txt](https://smartletter.ai/llms-full.txt) ## Authentication Send your key in the `Authorization` header: `Authorization: Bearer sl_…`. Requests also need a `User-Agent` header (every HTTP client sends one). | Level | Can do | Where to use it | | --- | --- | --- | | `full_access` | Everything | Your servers, CI, agents | | `sending_access` | `POST /emails` only (optionally one domain) | Apps that only send transactional email | | `signups` | `POST /signups` only | Your site's signup form, in public code | A workspace can have 20 active keys. A token is shown once, at creation; we only store its hash. Revoking a key stops it at once. ### Signup form example A `signups` key can only add pending contacts, who must confirm by email. It is safe to ship in your page: ```html
``` ## Errors Errors use the Resend format, so the Resend SDKs show them as `{ data: null, error }`: ```json { "name": "validation_error", "statusCode": 422, "message": "Invalid `subject`: Too small: expected string to have >=1 characters" } ``` Some errors add `details`: the blocking items of the pre-send review, for example. | Name | Status | Meaning | | --- | --- | --- | | `missing_api_key` | 401 | No `Authorization: Bearer` header. | | `restricted_api_key` | 401 | The key's level does not allow this route (e.g. a `signups` key on `/contacts`). | | `invalid_api_key` | 403 | Unknown or revoked key, or a Resend key (`re_…`). | | `validation_error` | 422 | The body or a parameter is invalid; `message` names the field. | | `invalid_parameter` | 422 | A path id or cursor is malformed or unknown. | | `missing_required_field` | 422 | A required field is missing. | | `presend_blocked` | 422 | The pre-send review found blocking items; they are in `details.items`. | | `not_found` | 404 | No such resource in your workspace. | | `invalid_state` | 409 | The resource is not in a state that allows this (e.g. pausing an issue that is not sending). | | `under_review` | 409 | The issue is held for a quick content review by our team. | | `conflict` | 409 | The resource already exists, or can never be changed (e.g. a bounce suppression). | | `plan_limit_reached` | 403 | Your plan's limit is reached (subscribers, keys, images). | | `workspace_suspended` | 403 | The workspace is suspended; contact support. | | `rate_limit_exceeded` | 429 | Too many requests; see the `ratelimit-*` and `retry-after` headers. | | `monthly_quota_exceeded` | 429 | The monthly email quota of your plan is used up. | | `invalid_idempotency_key` | 400 | The Idempotency-Key must be 1–256 characters. | | `concurrent_idempotent_requests` | 409 | The same Idempotency-Key is still being processed. | | `invalid_idempotent_request` | 409 | The same Idempotency-Key was used with another body. | | `payload_too_large` | 413 | The upload is over the size limit. | | `unsupported_media_type` | 415 | The upload is not a PNG, JPEG, GIF or WebP image. | | `application_error` | 500 | Unexpected error on our side; retry later. | | `service_unavailable` | 503 | A dependency is temporarily unavailable; retry later. | ## Limits | Limit | Value | | --- | --- | | Per workspace | 10 requests / second (all keys together) | | Per key | 300 requests / minute | | Signups | 5 per IP address per 10 minutes | | Import | 1000 contacts per call | | Images | 4 MB, PNG / JPEG / GIF / WebP | | Monthly emails | Your plan's quota (see `GET /usage`) | Every answer carries `ratelimit-limit`, `ratelimit-remaining` and `ratelimit-reset` (seconds). A 429 adds `retry-after`. ### Pagination Lists are newest first: `{ "object": "list", "has_more": true, "data": [...] }`. Pass `limit` (1–100, default 20), then `after=` for the next page or `before=` for the previous one. ### Idempotency Send an `Idempotency-Key` header on sends, imports and signups. The same key with the same body replays the first answer for 24 hours; with another body it is refused. ## MCP server The Smartletter MCP server gives an AI agent the same actions as this API: write an issue, review it, send yourself a test, send to your most engaged contacts first, pause, read the report per mailbox provider, import contacts with cleaning. URL: `https://smartletter.ai/mcp`. ### OAuth, not API keys The MCP server signs you in with OAuth: your client opens a Smartletter page, you pick the workspace and approve. The agent then acts as you, with your role (a viewer cannot send). Revoke access any time from Settings. API keys are for your own code; the MCP server does not take them. ### Claude - Claude.ai and Claude Desktop: Settings → Connectors → Add custom connector, URL `https://smartletter.ai/mcp`. - Claude Code: `claude mcp add --transport http smartletter https://smartletter.ai/mcp`. ### ChatGPT - Settings → Connectors → Create (developer mode), URL `https://smartletter.ai/mcp`, authentication OAuth. ### Cursor ```json { "mcpServers": { "smartletter": { "url": "https://smartletter.ai/mcp" } } } ``` ### Safety Tools that send email or delete data are marked destructive, so clients ask you before running them. Sends follow the same rules as the dashboard: pre-send review, warm-up, test batch, automatic pause on bounces or complaints. ## Emails Transactional email, compatible with the Resend API and SDKs. With the Resend SDK: ```ts import { Resend } from "resend"; // The official Resend SDK, pointed at Smartletter. const resend = new Resend(process.env.SMARTLETTER_API_KEY, { baseUrl: "https://api.smartletter.ai" }); const { data, error } = await resend.emails.send({ from: "Acme ", to: ["jane@example.com"], subject: "Welcome", html: "

Hi Jane

", }); ``` ### POST /emails — Send an email Sends one transactional email, like Resend's `POST /emails`. Works with the official Resend SDKs: point them at `https://api.smartletter.ai`. The `from` domain must be verified in your workspace. Keys: `full_access`, `sending_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `from` | string | yes | | | `to` | string \| string[] | yes | | | `subject` | string | yes | | | `html` | string | no | | | `text` | string | no | | | `cc` | string \| string[] | no | | | `bcc` | string \| string[] | no | | | `reply_to` | string \| string[] | no | | | `headers` | object | no | | | `tags` | object[] | no | | ```bash curl -X POST https://api.smartletter.ai/emails \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"from":"Acme ","to":["jane@example.com"],"subject":"Welcome","html":"

Hi Jane

"}' ``` Response 200: ```json { "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" } ``` ### GET /emails/{id} — Retrieve an email Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/emails/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "email", "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", "last_event": "delivered" } ``` ## Newsletters Newsletter issues (campaigns): write, review, test, send, schedule, pause, report. ### GET /newsletters — List newsletters Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `status` | "draft" \| "scheduled" \| "sending" \| "paused" \| "sent" \| "failed" \| "held" | no | | | `limit` | integer | no | 1–100, default 20. | | `after` | uuid | no | Id of the last item of the previous page (older items). | | `before` | uuid | no | Id of the first item of the previous page (newer items). | ```bash curl -X GET https://api.smartletter.ai/newsletters \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "topic_id": null, "subject": "This week in AI", "preheader": "Three tools worth your time", "status": "draft", "body": { "format": "markdown", "content": "# Hello\n\nThree tools this week…" }, "audience_limit": null, "scheduled_for": null, "pause_reason": null, "recipient_estimate": null, "created_at": "2026-10-17T09:00:00.000Z", "updated_at": "2026-10-17T09:00:00.000Z" } ] } ``` ### POST /newsletters — Create a draft Creates a draft from a Markdown or HTML body. The platform adds the footer (unsubscribe link, postal address) when the issue is sent. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `subject` | string | yes | Subject line. | | `preheader` | string | no | Preview text shown after the subject. | | `segment_id` | uuid | no | Newsletter (segment) to send from. Optional when the workspace has a single active newsletter. | | `topic_id` | uuid | no | Topic of that newsletter; its subscribers receive the issue. | | `markdown` | string | no | Body in Markdown. Give `markdown` or `html`. | | `html` | string | no | Body in HTML. The platform footer is added around it. | ```bash curl -X POST https://api.smartletter.ai/newsletters \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"This week in AI","preheader":"Three tools worth your time","markdown":"# Hello\n\nThree tools this week…"}' ``` Response 201: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "topic_id": null, "subject": "This week in AI", "preheader": "Three tools worth your time", "status": "draft", "body": { "format": "markdown", "content": "# Hello\n\nThree tools this week…" }, "audience_limit": null, "scheduled_for": null, "pause_reason": null, "recipient_estimate": null, "created_at": "2026-10-17T09:00:00.000Z", "updated_at": "2026-10-17T09:00:00.000Z" } ``` ### GET /newsletters/{id} — Retrieve a newsletter Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/newsletters/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "topic_id": null, "subject": "This week in AI", "preheader": "Three tools worth your time", "status": "draft", "body": { "format": "markdown", "content": "# Hello\n\nThree tools this week…" }, "audience_limit": null, "scheduled_for": null, "pause_reason": null, "recipient_estimate": null, "created_at": "2026-10-17T09:00:00.000Z", "updated_at": "2026-10-17T09:00:00.000Z" } ``` ### PATCH /newsletters/{id} — Update a draft Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `subject` | string | no | | | `preheader` | string | no | | | `topic_id` | uuid | no | | | `markdown` | string | no | | | `html` | string | no | | ```bash curl -X PATCH https://api.smartletter.ai/newsletters/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"This week in AI (updated)"}' ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "topic_id": null, "subject": "This week in AI", "preheader": "Three tools worth your time", "status": "draft", "body": { "format": "markdown", "content": "# Hello\n\nThree tools this week…" }, "audience_limit": null, "scheduled_for": null, "pause_reason": null, "recipient_estimate": null, "created_at": "2026-10-17T09:00:00.000Z", "updated_at": "2026-10-17T09:00:00.000Z" } ``` ### DELETE /newsletters/{id} — Delete a draft Only drafts can be deleted. Unschedule a scheduled issue first. Keys: `full_access`. ```bash curl -X DELETE https://api.smartletter.ai/newsletters/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "deleted": true } ``` ### POST /newsletters/{id}/duplicate — Duplicate Copies subject, preheader, body and targeting into a new draft. Never the sending state or the stats. Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/newsletters//duplicate \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 201: ```json { "object": "newsletter", "id": "9a0d2f3e-0000-4000-8000-000000000004", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "topic_id": null, "subject": "This week in AI", "preheader": "Three tools worth your time", "status": "draft", "body": { "format": "markdown", "content": "# Hello\n\nThree tools this week…" }, "audience_limit": null, "scheduled_for": null, "pause_reason": null, "recipient_estimate": null, "created_at": "2026-10-17T09:00:00.000Z", "updated_at": "2026-10-17T09:00:00.000Z" } ``` ### GET /newsletters/{id}/review — Pre-send review Runs the checks a send runs (subject, unsubscribe link, Gmail clipping at ~102 KB, postal address, sending domain) without sending. Blocking items make `send` and `schedule` fail with 422. Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/newsletters//review \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "review", "blocking": false, "items": [ { "id": "subject", "level": "ok" }, { "id": "gmail_clip", "level": "ok", "values": { "kb": 18 } } ] } ``` ### POST /newsletters/{id}/test — Send a test email Sends the issue to the workspace owner only, subject prefixed with [Test]. 10 per hour; counts in the monthly quota. Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/newsletters//test \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "test_email", "to": "owner@acme.com" } ``` ### POST /newsletters/{id}/send — Send now Locks the rendered issue and starts sending. Warm-up, test batch and automatic pauses apply. `audience_limit` sends to the N most engaged contacts first. A blocking pre-send item answers 422 `presend_blocked` with the items in `details`. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `audience_limit` | integer | no | Send only to the N most engaged contacts now; the rest can follow with send-more. | ```bash curl -X POST https://api.smartletter.ai/newsletters//send \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"audience_limit":500}' ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "sending" } ``` ### POST /newsletters/{id}/schedule — Schedule Schedules a draft up to one year ahead. Same checks as a send. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `scheduled_at` | datetime | yes | ISO 8601 date, in the future, at most one year ahead. | | `audience_limit` | integer | no | Send only to the N most engaged contacts now; the rest can follow with send-more. | ```bash curl -X POST https://api.smartletter.ai/newsletters//schedule \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"scheduled_at":"2026-10-20T08:00:00Z"}' ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "scheduled", "scheduled_for": "2026-10-20T08:00:00.000Z" } ``` ### POST /newsletters/{id}/unschedule — Unschedule Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/newsletters//unschedule \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "draft" } ``` ### POST /newsletters/{id}/pause — Pause sending Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/newsletters//pause \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "paused" } ``` ### POST /newsletters/{id}/resume — Resume sending Resumes a manual pause (or a quota / suspension pause once lifted). A pause for bounces, complaints or a failed test batch needs our team: 409. Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/newsletters//resume \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "sending" } ``` ### POST /newsletters/{id}/send-more — Send to the next N For an issue sent with an `audience_limit`: sends to the next `count` most engaged contacts. Nobody gets it twice. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `count` | integer | yes | How many of the next most engaged contacts to add. | ```bash curl -X POST https://api.smartletter.ai/newsletters//send-more \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"count":1000}' ``` Response 200: ```json { "object": "newsletter", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "sending" } ``` ### GET /newsletters/{id}/report — Report Opens, clicks, bounces, complaints and unsubscribes, sending progress, and results per mailbox provider. Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/newsletters//report \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "report", "id": "7c3e1a52-0000-4000-8000-000000000001", "status": "sent", "totals": { "sent": 1200, "delivered": 1188, "opened": 540, "clicked": 96, "bounced": 12, "complained": 0, "unsubscribed": 4 }, "rates": { "delivery": 0.99, "open": 0.4545, "click": 0.0808, "bounce": 0.01, "complaint": 0, "unsubscribe": 0.0034 }, "progress": { "attempted": 1200, "recipient_estimate": 1200, "audience_limit": null }, "providers": [ { "provider": "gmail", "sent": 700, "delivered": 698, "opened": 330, "clicked": 60, "bounced": 2, "complained": 0 } ] } ``` ## Contacts Subscribers of your newsletters: add, import with cleaning, export, unsubscribe. ### GET /contacts — List contacts Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `segment_id` | uuid | no | | | `status` | "active" \| "pending" \| "unsubscribed" \| "bounced" \| "complained" \| "dormant" | no | | | `search` | string | no | Substring of the email address. | | `limit` | integer | no | 1–100, default 20. | | `after` | uuid | no | Id of the last item of the previous page (older items). | | `before` | uuid | no | Id of the first item of the previous page (newer items). | ```bash curl -X GET https://api.smartletter.ai/contacts \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "email": "jane@example.com", "status": "active", "segment_id": "2d9e4b11-0000-4000-8000-000000000003" } ] } ``` ### POST /contacts — Add a contact Adds one contact as imported (the same cleaning as an import: invalid syntax, typos, spam traps, role and disposable addresses, suppressions). A refused address answers 422 with the reason. Returns the existing contact when the address is already there. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | | | `segment_id` | uuid | no | Optional when the workspace has a single active newsletter. | | `name` | string | no | | | `language` | string | no | | ```bash curl -X POST https://api.smartletter.ai/contacts \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"jane@example.com","name":"Jane"}' ``` Response 201: ```json { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "email": "jane@example.com", "status": "active", "existed": false } ``` ### POST /contacts/import — Import contacts Up to 1000 rows per call, `consent: true` required. Rows are cleaned like a CSV import and the answer reports every rejected row with its reason. New accounts' large imports may be held for a quick review. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `segment_id` | uuid | no | | | `consent` | true | yes | You confirm every contact opted in to receive this newsletter (no bought, rented or scraped list). | | `contacts` | object[] | yes | | ```bash curl -X POST https://api.smartletter.ai/contacts/import \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"consent":true,"contacts":[{"email":"jane@example.com","name":"Jane","open_rate":0.42}]}' ``` Response 200: ```json { "object": "import", "total": 3, "inserted": 1, "rejected": 2, "reasons": { "invalid": 0, "typo": 1, "spam_trap": 0, "role": 1, "disposable": 0, "no_mx": 0, "duplicate": 0, "suppressed": 0, "capped": 0 }, "rejected_rows": [ { "line": 2, "email": "bob@gmial.com", "reason": "typo", "suggestion": "bob@gmail.com" }, { "line": 3, "email": "info@acme.com", "reason": "role" } ], "pending_review": false } ``` ### GET /contacts/export — Export contacts (CSV) Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `segment_id` | uuid | no | | | `status` | "active" \| "pending" \| "unsubscribed" \| "bounced" \| "complained" \| "dormant" | no | | ```bash curl -X GET https://api.smartletter.ai/contacts/export \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` ### GET /contacts/{id} — Retrieve a contact Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/contacts/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "email": "jane@example.com", "status": "active", "engagement": { "emails_sent": 12, "emails_opened": 7, "emails_clicked": 2 } } ``` ### POST /contacts/{id}/unsubscribe — Unsubscribe Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/contacts//unsubscribe \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "status": "unsubscribed" } ``` ### POST /contacts/{id}/reactivate — Reactivate a dormant contact Only for contacts put to sleep by the inactive cleanup. An unsubscribed contact can only come back through their own link. Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/contacts//reactivate \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "status": "active" } ``` ### DELETE /contacts/{id} — Delete a contact Keys: `full_access`. ```bash curl -X DELETE https://api.smartletter.ai/contacts/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "contact", "id": "5b1f2c90-0000-4000-8000-000000000002", "deleted": true } ``` ## Signups Double opt-in signups from your own site, with a key that is safe in public code. ### POST /signups — Sign up a subscriber For your site's signup form. Use a `signups` key: it can do nothing else, so it is safe in public code. The contact stays pending until they click the confirmation email (double opt-in). The answer is the same for every address. Limited to 5 signups per IP per 10 minutes. Keys: `signups`, `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | | | `name` | string | no | | | `segment_id` | uuid | no | Optional when the workspace has a single active newsletter. | | `language` | string | no | | | `source_url` | string | no | Page the form was on (attribution). | ```bash curl -X POST https://api.smartletter.ai/signups \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"jane@example.com","name":"Jane","source_url":"https://acme.com/blog"}' ``` Response 202: ```json { "object": "signup", "status": "pending" } ``` ## Segments Your newsletters (segments) and their topics. ### GET /segments — List newsletters (segments) Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default 20. | | `after` | uuid | no | Id of the last item of the previous page (older items). | | `before` | uuid | no | Id of the first item of the previous page (newer items). | ```bash curl -X GET https://api.smartletter.ai/segments \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "segment", "id": "2d9e4b11-0000-4000-8000-000000000003", "name": "Acme Weekly", "slug": "acme-weekly" } ] } ``` ### POST /segments — Create a newsletter (segment) Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `name` | string | yes | | | `slug` | string | yes | | | `language` | string | no | | | `description` | string | no | | | `themes` | string[] | no | | ```bash curl -X POST https://api.smartletter.ai/segments \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"Acme Weekly","slug":"acme-weekly","language":"en"}' ``` Response 201: ```json { "object": "segment", "id": "2d9e4b11-0000-4000-8000-000000000003", "name": "Acme Weekly", "slug": "acme-weekly" } ``` ### GET /topics — List topics Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `segment_id` | uuid | yes | | ```bash curl -X GET https://api.smartletter.ai/topics \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "topic", "id": "5b1f2c90-0000-4000-8000-000000000002", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "name": "AI tools" } ] } ``` ## Archive Public archive of a newsletter's sent issues, and its custom subdomain (tracked links, preferences, archive). ### GET /segments/{id}/archive — Retrieve a newsletter's archive settings Settings of the public archive and its URL (on the custom subdomain once verified). Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/segments//archive \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "archive", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "enabled": true, "slug": "acme-weekly", "url": "https://news.acme.com/", "custom_domain": "news.acme.com", "description": "Three AI tools every Tuesday", "author": "Jane Doe", "about": null, "avatar_url": null, "links": { "x": "https://x.com/acme" } } ``` ### PATCH /segments/{id}/archive — Update a newsletter's archive settings Only sent issues are public, never hidden ones nor issues reserved to a plan. The archive is indexed by search engines (sitemap, RSS at `/rss.xml`). Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `enabled` | boolean | no | Publish the sent issues of this newsletter on its public archive. | | `slug` | string | no | Archive address: `smartletter.ai//n/`. Unique across Smartletter. | | `description` | string | no | One-line description (also the meta description). | | `author` | string | no | | | `about` | string | no | Text of the About page (plain text, blank lines between paragraphs). | | `avatar_url` | string | no | https URL of an avatar or logo. | | `links` | object | no | Social links (https). Replaces the whole set. | ```bash curl -X PATCH https://api.smartletter.ai/segments//archive \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled":true,"description":"Three AI tools every Tuesday","links":{"x":"https://x.com/acme"}}' ``` Response 200: ```json { "object": "archive", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "enabled": true, "slug": "acme-weekly", "url": "https://news.acme.com/", "custom_domain": "news.acme.com", "description": "Three AI tools every Tuesday", "author": "Jane Doe", "about": null, "avatar_url": null, "links": { "x": "https://x.com/acme" } } ``` ### PATCH /newsletters/{id}/archive — Pin or hide an issue in the archive Sent issues only. At most 3 pinned issues per newsletter (409 beyond). Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `pinned` | boolean | no | Pin at the top of the archive (3 pinned issues max per newsletter). | | `hidden` | boolean | no | Hide this issue from the public archive. | ```bash curl -X PATCH https://api.smartletter.ai/newsletters//archive \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"pinned":true}' ``` Response 200: ```json { "object": "newsletter_archive", "id": "7c3e1a52-0000-4000-8000-000000000001", "pinned": true, "hidden": false } ``` ### GET /segments/{id}/domain — Retrieve a newsletter's custom subdomain Status and the DNS record to add. Links of new emails use the subdomain once it is verified. Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/segments//domain \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "custom_domain", "id": "5b1f2c90-0000-4000-8000-000000000002", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "domain": "news.acme.com", "status": "pending", "uses": [ "tracking", "preferences", "archive" ], "records": [ { "type": "CNAME", "name": "news.acme.com", "value": "abc123.vercel-dns-017.com" } ], "last_checked_at": null, "verified_at": null, "last_error": null, "created_at": "2026-10-17T09:00:00.000Z" } ``` ### POST /segments/{id}/domain — Add a custom subdomain One subdomain per newsletter (`news.acme.com` by default in the dashboard) for tracked links, the preference and unsubscribe pages and the public archive. Pro plan or higher. Add the returned CNAME (DNS only, not proxied); verification runs every 15 minutes. Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `domain` | string | yes | Subdomain you own, e.g. `news.acme.com` (a CNAME to Vercel). | ```bash curl -X POST https://api.smartletter.ai/segments//domain \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain":"news.acme.com"}' ``` Response 201: ```json { "object": "custom_domain", "id": "5b1f2c90-0000-4000-8000-000000000002", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "domain": "news.acme.com", "status": "pending", "uses": [ "tracking", "preferences", "archive" ], "records": [ { "type": "CNAME", "name": "news.acme.com", "value": "abc123.vercel-dns-017.com" } ], "last_checked_at": null, "verified_at": null, "last_error": null, "created_at": "2026-10-17T09:00:00.000Z" } ``` ### DELETE /segments/{id}/domain — Remove the custom subdomain New emails go back to smartletter.ai links at once. Remove the CNAME too. Keys: `full_access`. ```bash curl -X DELETE https://api.smartletter.ai/segments//domain \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "custom_domain", "segment_id": "2d9e4b11-0000-4000-8000-000000000003", "deleted": true } ``` ## Suppressions Addresses that never receive anything from your workspace. ### GET /suppressions — List suppressions Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `reason` | "bounced" \| "complained" \| "user_unsub" \| "admin" \| "gdpr" | no | | | `limit` | integer | no | 1–100, default 20. | | `after` | uuid | no | Id of the last item of the previous page (older items). | | `before` | uuid | no | Id of the first item of the previous page (newer items). | ```bash curl -X GET https://api.smartletter.ai/suppressions \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "suppression", "id": "5b1f2c90-0000-4000-8000-000000000002", "email": "old@example.com", "reason": "admin" } ] } ``` ### POST /suppressions — Suppress an address Keys: `full_access`. Body: | Field | Type | Required | Description | | --- | --- | --- | --- | | `email` | string | yes | | | `reason` | "bounced" \| "complained" \| "user_unsub" \| "admin" \| "gdpr" | no | | | `notes` | string | no | | ```bash curl -X POST https://api.smartletter.ai/suppressions \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email":"old@example.com","reason":"gdpr"}' ``` Response 201: ```json { "object": "suppression", "id": "5b1f2c90-0000-4000-8000-000000000002", "email": "old@example.com", "reason": "gdpr" } ``` ### DELETE /suppressions/{id} — Lift a suppression Bounces and spam complaints can never be lifted (409). Keys: `full_access`. ```bash curl -X DELETE https://api.smartletter.ai/suppressions/ \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "suppression", "id": "5b1f2c90-0000-4000-8000-000000000002", "deleted": true } ``` ## Images Images for your newsletters, hosted on the Smartletter CDN. ### GET /images — List images Keys: `full_access`. Query parameters: | Field | Type | Required | Description | | --- | --- | --- | --- | | `limit` | integer | no | 1–100, default 20. | | `after` | uuid | no | Id of the last item of the previous page (older items). | | `before` | uuid | no | Id of the first item of the previous page (newer items). | ```bash curl -X GET https://api.smartletter.ai/images \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "list", "has_more": false, "data": [ { "object": "image", "id": "5b1f2c90-0000-4000-8000-000000000002", "url": "https://assets.smartletter.ai/…/x.png" } ] } ``` ### POST /images — Upload an image PNG, JPEG, GIF or WebP, 4 MB max. Send the file as the raw body (`Content-Type: image/png`…) or as multipart field `file`. The type is read from the bytes. Keys: `full_access`. ```bash curl -X POST https://api.smartletter.ai/images \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" \ -H "Content-Type: image/png" \ --data-binary @image.png ``` Response 201: ```json { "object": "image", "id": "5b1f2c90-0000-4000-8000-000000000002", "url": "https://assets.smartletter.ai/ws/5b1f2c90.png", "content_type": "image/png", "size_bytes": 48213 } ``` ## Stats Audience growth and sending volume. ### GET /stats/overview — Overview Active subscribers, new subscribers and unsubscribes per day over the last 30 days, and emails sent. Keys: `full_access`. ```bash curl -X GET https://api.smartletter.ai/stats/overview \ -H "Authorization: Bearer $SMARTLETTER_API_KEY" ``` Response 200: ```json { "object": "overview", "subscribers": { "active": 1520, "new_last_30_days": 210, "unsubscribed_last_30_days": 12 }, "daily": [ { "date": "2026-10-17", "new_subscribers": 9, "unsubscribed": 0, "emails_sent": 0 } ], "emails": { "sent_this_month": 4800, "sent_last_30_days": 6100, "newsletters_sent_last_30_days": 4 } } ``` ## Domains Custom sending domains (DKIM, SPF, MX). ### POST /domains — Add a domain `tracking_subdomain` (a label, e.g. `news`) also makes `