Email marketing
Send a one-off broadcast email to a segment of your project's users, such as verified, new, active or dormant users, through your project's SMTP.
Email marketing sends a single subject and body to a group of your project's end users, chosen by a built-in segment. OrbitNest finds the recipients on the server and sends in the background through your project's SMTP. Only workspace admins can send, from Studio or with the admin API.
Requires project SMTP
Broadcasts always go through the project's own SMTP. The shared OrbitNest sender only carries auth emails. Without SMTP, a broadcast starts but every recipient fails.
Who receives broadcasts
Recipients come from your project's auth_users table, meaning the people who signed up to your app. Deleted users, banned users, users without an email address and unsubscribed addresses are always skipped. You cannot upload or paste your own list of recipients.
Segments
| Segment | Studio label | Who it includes |
|---|---|---|
all | All users | Every user, verified or not |
verified | Verified | Users who confirmed their email |
unverified | Unverified | Users who never confirmed their email |
recent_signups_7d | New (7 days) | Users created in the last 7 days |
recent_signins_7d | Active (7 days) | Users who signed in within the last 7 days |
inactive_30d | Dormant 30+ | Verified users with no sign-in in the last 30 days, including verified users who never signed in |
inactive_60d | Dormant 60+ | The same, for 60 days |
inactive_90d | Dormant 90+ | The same, for 90 days |
never_signed_in | Never signed in | Users who never signed in |
Segments are worked out when you send, so they always reflect current data. Each broadcast reaches at most 10,000 recipients. If a segment is larger, only the 10,000 most recently created users receive it.
Send from Studio
Open your project and go to Settings → Marketing.
- Audience: pick a segment. Studio shows how many recipients it matches, a sample of their addresses, and how many were left out because they unsubscribed.
- Template: optionally start from Announcement, Newsletter, Re-engagement or Custom. Each one fills in a sample subject and HTML body.
- Message: edit the Subject, the HTML body (use inline styles) and an optional Plain-text fallback.
- Click Send broadcast, review the recipient count, and confirm. OrbitNest adds an unsubscribe link to the footer of every email.
The broadcast runs in the background. When it finishes you can see how many emails were sent and how many failed, with the failure reasons.
Caution
A broadcast cannot be stopped or undone once it starts. Check the recipient count and sample before you confirm.
Send with the API
These endpoints use a Studio admin access token, which is the access_token returned by POST /api/auth/signin. :projectId must be the project UUID.
| Method | Path | Role | Description |
|---|---|---|---|
GET | /api/admin/projects/:projectId/email/audience?segment=<segment>&limit=<n> | Any member | Count the recipients of a segment and return up to 50 sample addresses, without sending. limit must be a positive integer |
POST | /api/admin/projects/:projectId/email/broadcast | Admin | Start a broadcast, or do a dry run. Limited to 10 requests per minute per IP address |
GET | /api/admin/projects/:projectId/email/broadcast/:jobId | Any member | Progress and result of a broadcast |
GET | /api/admin/projects/:projectId/email/suppressions?search=<text>&limit=<n> | Any member | List unsubscribed addresses, newest first. limit is 1 to 1,000 (default 200) |
DELETE | /api/admin/projects/:projectId/email/suppressions/:email | Admin | Remove an address from the unsubscribe list. Returns 404 if it is not on the list |
Preview the audience
curl "https://api.orbitnest.io/api/admin/projects/$PROJECT_ID/email/audience?segment=inactive_30d" \
-H "Authorization: Bearer $ORBITNEST_ADMIN_TOKEN"{
"success": true,
"data": {
"recipientCount": 128,
"sampleRecipients": ["ana@example.com", "li@example.com"],
"suppressedCount": 3
}
}suppressedCount is the number of segment members left out because they unsubscribed.
Send a broadcast
| Field | Type | Required | Description |
|---|---|---|---|
segment | string | Yes | One of the segments above. Any other value returns 400 |
subject | string | Yes | The subject line, up to 998 characters |
html | string | Yes | The HTML body |
text | string | No | A plain-text alternative |
dryRun | boolean | No | If true, finds the recipients and returns them without sending |
limit | integer | No | The maximum number of recipients, 1 to 10,000 |
Unknown fields return 400.
curl -X POST "https://api.orbitnest.io/api/admin/projects/$PROJECT_ID/email/broadcast" \
-H "Authorization: Bearer $ORBITNEST_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"segment": "inactive_30d",
"subject": "Here is what is new in My App",
"html": "<p>We shipped a lot since your last visit. Come take a look.</p>",
"text": "We shipped a lot since your last visit. Come take a look."
}'A real send returns 202 Accepted right away with a job:
{
"success": true,
"data": {
"jobId": "3c9a1f2e-7b4d-4c1a-9e0f-5d2b8a6c4e11",
"status": "running",
"startedAt": "2026-10-12T09:30:00.000Z",
"finishedAt": null,
"segment": "inactive_30d",
"recipientCount": 128,
"sampleRecipients": ["ana@example.com", "li@example.com"],
"sent": 0,
"failed": 0,
"errors": [],
"dryRun": false,
"suppressedCount": 3
}
}A dry run, or a segment with no recipients, returns 200 with the same fields but no job (jobId, status, startedAt and finishedAt are left out). recipientCount never includes unsubscribed addresses.
Check progress
Poll the job until status is completed:
curl "https://api.orbitnest.io/api/admin/projects/$PROJECT_ID/email/broadcast/$JOB_ID" \
-H "Authorization: Bearer $ORBITNEST_ADMIN_TOKEN"The response has the same shape (without suppressedCount), with sent and failed counting up and finishedAt set at the end. If someone unsubscribes while the broadcast is sending, they are skipped and recipientCount goes down by one. errors lists up to 200 failures as { "email", "reason" }. sampleRecipients lists at most 50 addresses. An unknown job returns 404. Finished jobs are kept for 30 days.
When a broadcast is refused
POST .../email/broadcast returns 409 Conflict when:
- another broadcast is still sending for the project, or
- the same subject, HTML, text and segment were already delivered to at least one recipient in the past hour. Change the content to send again.
Unsubscribe
Every broadcast email gets:
- an Unsubscribe link at the end of the HTML body (and of the plain-text body, if you sent one), and
- the
List-UnsubscribeandList-Unsubscribe-Post: List-Unsubscribe=One-Clickheaders, so mail apps such as Gmail and Apple Mail can show their own unsubscribe button.
Both point to https://api.orbitnest.io/api/email/unsubscribe?token=<token>. The server signs the token, and it is tied to one project and one address, so nobody can guess it or change it to unsubscribe someone else.
- Opening the link (
GET) shows a confirmation page with an Unsubscribe button. Opening it alone does not unsubscribe, because email security scanners open links automatically. POSTto the same URL unsubscribes. The button and the mail apps' one-click button both do this. It returns{ "success": true, "data": { "unsubscribed": true } }, or a confirmation page for a browser. Repeating it is harmless.- An invalid or edited token returns
400. - The endpoint needs no login and is limited to 120 requests per minute per IP address.
Unsubscribing applies to one project's broadcasts only. Sign-in codes, verification, password-reset and welcome emails, and emails sent with the Email API, are not affected.
To see who unsubscribed, open Settings → Marketing and click View list under Unsubscribed. Admins can remove an address there (for example, when the person asks to hear from you again), and the address receives the next broadcast.
Delivery behavior
- Each broadcast keeps up to four connections open to the project's SMTP server and sends four emails at a time over them. If one recipient fails, the rest are still sent.
- If SMTP is not configured or is turned off, every recipient fails.
- Every message uses the project's From address, with Reply-To set to the From Email.
- Jobs and their recipient lists are stored in the database. If the API restarts or is redeployed while a broadcast is sending, the broadcast continues from where it stopped and skips everyone who already got it. Only one API server sends a given broadcast at a time.
- If an API server crashes in the middle of a send, the few emails in flight at that moment can go out a second time when the broadcast resumes. After a crash, sending can pause for up to five minutes before another server picks it up.
Current limitations
- No personalization. The subject and body are sent exactly as written, so placeholders such as
{{user_name}}arrive as literal text. - No scheduling, saved campaigns or send history. Each broadcast is a one-off request. Only its job shows what happened, for 30 days, and there is no list of past jobs.
- No open or click tracking.
- Built-in segments only. You cannot define custom segments or filter on your own consent fields.
Consent is your responsibility
Only email users who expect to hear from you. OrbitNest handles unsubscribes from broadcasts, but laws such as CAN-SPAM and GDPR can require more, such as recording consent or including your postal address. Add anything else your audience needs to the body yourself.
For a single email to one recipient, use the Email API.