Send a custom email
Send an email from your publication to a list of recipient addresses.
Eligibility:
- Publications must be approved by Paragraph before they can send custom emails. Ineligible publications receive a 403. Eligibility is managed by Paragraph and is not user-configurable.
Per-recipient filtering:
- Malformed addresses and known disposable domains are skipped.
- Addresses that previously unsubscribed from this publication are skipped as
suppressed. - Skipped recipients are returned in the response; nothing else is delivered to them.
Delivery:
bodyis treated as Markdown and rendered to HTML server-side.- Each recipient receives the email individually (not as a BCC blast) with a mandatory unsubscribe footer.
- Sends are queued asynchronously; a 200 response means recipients were accepted, not delivered.
Caps:
- Maximum of 10,000 addresses per call (request-level sanity check).
apiKeyAuthorizationBearer <token>API key for authenticating protected endpoints. Pass as Bearer token in Authorization header.
Body
application/json- body
subject*stringSubject line of the email
1 <= length <= 998body*stringEmail body. Markdown; rendered to HTML server-side. Max 100KB.
1 <= length <= 100000emails*array<>Recipient email addresses (max 10,000). Malformed addresses are returned in skipped with reason: "invalid" rather than rejecting the whole request.
1 <= items <= 10000dryRun?booleanIf true, run filtering and return the accepted/skipped split without scheduling delivery
Send request accepted
application/json- response
accepted*integerNumber of recipients queued for delivery (or that would be queued when dryRun is true)
skipped*array<>Recipients that were rejected, with the reason each one was skipped. scheduling_failed means filtering passed but the delivery task could not be queued — retry these addresses.
curl -X POST "https://example.com/v1/emails/send" \ -H "Content-Type: application/json" \ -d '{ "subject": "string", "body": "string", "emails": [ "string" ] }'{ "accepted": 0, "skipped": [ { "email": "string", "reason": "suppressed" } ]}Remove a subscriber DELETE
Remove a subscriber from your publication. The publication is identified by the API key provided in the Authorization header. Identify the subscriber by email, wallet, or both. **Requirements:** - At least one of `email` or `wallet` must be provided - If both are provided, they must resolve to the same subscriber **Behavior:** - The subscription is hard-deleted, mirroring the dashboard's "remove subscriber" action
Run an analytics SQL query POST
Execute a read-only SQL query against the analytics schema, scoped to your publication. **Auth:** Requires an API key. Queries run as the publication that owns the API key; Row-Level Security guarantees that only that publication's rows are visible even if the SQL is unscoped. **SQL requirements:** - `SELECT` or `WITH` (CTE) statements only - Reference tables unprefixed (e.g. `FROM posts`) — the `analytics` schema is the default `search_path` - No semicolons, no writes, no DDL, no superuser functions - Hard limit of 10,000 rows; anything over is truncated and `truncated: true` is returned - 30-second statement timeout **Discovering the schema:** call `GET /v1/analytics/schema` for column metadata. Common queries: open rate, subscriber count, top posts by views, engagement over time, click-through rate. For raw post-scoped tables, join through `posts.draft_of` to roll draft/version rows up to the canonical published post.