Newsletter
Subscribers API
5 endpoints under https://api.mailneo.co/api/v2. Every request carries your key in the x-api-key header.
Scopes used on this page: subscribers:delete, subscribers:read, subscribers:write. Each endpoint below states the one it requires. Start with the quickstart →
/subscribersList newsletter subscribers
Requires subscribers:read
Cursor-paginated. Pass updated_since with the synced_through value from your previous run to fetch only what changed; you will occasionally re-see a row, so upsert by id. Deletes are not visible to updated_since.
Subscribers are distinct from contacts: a contact is a cold-outreach record, a subscriber is on a newsletter list. Creating one does not create the other.
Query parameters10
limitintegerdefault: 251..100cursorstring≤ 256 charsnext_cursor. Treat it as a black box: cursors are bound to the sort they were issued under and are validated strictly, so a hand-crafted, modified or foreign cursor — including any cursor issued before the 2026-08 format revision — answers 400 cursor_invalid. On that error, restart from the first page.order"asc" | "desc"default: descupdated_sincestring · date-timesort"updated_at" | "created_at"default: updated_atstatus"ENABLED" | "DISABLED" | "BLOCKLISTED"emailstring≤ 320 charssourcestring≤ 200 charstagsstring≤ 1000 charslist_idstring≤ 64 charscurl "https://api.mailneo.co/api/v2/subscribers?limit=25&order=desc&sort=updated_at" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": [
{
"object": "subscriber",
"id": "c0oevquz0o4w5600oyuy8x0oo",
"email": "ada@example.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+1 555 0142",
"company": "Acme Inc",
"status": "ENABLED",
"tags": [
"vip"
],
"source": "api",
"custom_fields": {
"plan": "pro"
},
"last_contacted_at": "2026-06-24T15:45:00.000Z",
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z"
}
],
"meta": {
"request_id": "633dbfb4-643d-4147-a53d-c2da663dc46d",
"has_more": false,
"next_cursor": null,
"synced_through": "2026-06-24T15:45:00.000Z"
}
}/subscribersCreate a subscriber
Requires subscribers:write · Idempotency-Key optional
Creates one subscriber. The email is normalised (trimmed and lower-cased) and must be unique within the team — a duplicate is a 409 resource_already_exists, not a silent upsert, unlike the v1 endpoint this replaces. When name is omitted it is composed from first_name and last_name, matching what the app does.
status accepts ENABLED or DISABLED. BLOCKLISTED cannot be written in either direction: it is the stored record of a global opt-out, owned by the unsubscribe and suppression machinery.
The new subscriber is on NO list — attach them with POST /lists/{listId}/subscribers, which requires lists:write and lets the list's own opt-in setting decide how the subscription starts. Subscribers are distinct from contacts: creating one does not create the other.
Automations do not run on subscribers created this way: a "subscriber is created" trigger will not fire, and no webhook is emitted.
Query parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.Header parameters1
Idempotency-Keystring≤ 255 charsIdempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours.Body parameters10
emailstringrequired3..320 charsnamestring≤ 200 charsfirst_namestring · nullable≤ 200 charslast_namestring · nullable≤ 200 charsphonestring · nullable≤ 50 charscompanystring · nullable≤ 200 charssourcestring · nullable≤ 200 charsstatus"ENABLED" | "DISABLED"tagsstring[]≤ 50 itemscustom_fieldsobjectcurl -X POST "https://api.mailneo.co/api/v2/subscribers" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+1 555 0142",
"company": "Acme Inc",
"source": "api",
"status": "ENABLED"
}'{
"data": "…",
"meta": {
"request_id": "45f78d41-44f7-4bae-a3f7-8a1b42f78888",
"dry_run": false
}
}/subscribers/{subscriberId}Retrieve a subscriber
Requires subscribers:read
Path parameters1
subscriberIdstringrequired1..64 charscurl "https://api.mailneo.co/api/v2/subscribers/{subscriberId}" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": {
"object": "subscriber",
"id": "c0oevquz0o4w5600oyuy8x0oo",
"email": "ada@example.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+1 555 0142",
"company": "Acme Inc",
"status": "ENABLED",
"tags": [
"vip"
],
"source": "api",
"custom_fields": {
"plan": "pro"
},
"last_contacted_at": "2026-06-24T15:45:00.000Z",
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z"
},
"meta": {
"request_id": "f6e83739-f5e8-45a6-a4e8-3413f3e83280"
}
}/subscribers/{subscriberId}Update a subscriber
Requires subscribers:write · Idempotency-Key optional
Merge-patch: fields you omit are left alone, and null clears a nullable field (name is not nullable — clear it with ""). tags REPLACES the whole array — v1 merged them, this does not — while custom_fields merges key by key so a value written by an import is not lost; clear one custom field by sending it as null.
Changing email is allowed and must not collide with another subscriber in the team (409 resource_already_exists).
A BLOCKLISTED subscriber — someone who globally opted out — keeps that status: sending status for them is a 409 resource_state_invalid, because re-enabling an opt-out is not a routine write. Their other fields can still be edited.
List membership is not editable here — that is POST /lists/{listId}/subscribers under lists:write. Field-update automations do not run on edits made this way.
Path parameters1
subscriberIdstringrequired1..64 charsQuery parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.Header parameters1
Idempotency-Keystring≤ 255 charsIdempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours.Body parameters10
emailstring3..320 charsnamestring≤ 200 charsfirst_namestring · nullable≤ 200 charslast_namestring · nullable≤ 200 charsphonestring · nullable≤ 50 charscompanystring · nullable≤ 200 charssourcestring · nullable≤ 200 charsstatus"ENABLED" | "DISABLED"tagsstring[]≤ 50 itemscustom_fieldsobjectcurl -X PATCH "https://api.mailneo.co/api/v2/subscribers/{subscriberId}" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"name": "Ada Lovelace",
"first_name": "Ada",
"last_name": "Lovelace",
"phone": "+1 555 0142",
"company": "Acme Inc",
"source": "api",
"status": "ENABLED"
}'{
"data": "…",
"meta": {
"request_id": "c53e40c4-c63e-4257-a73e-43eac83e457d",
"dry_run": false
}
}/subscribers/{subscriberId}Delete a subscriber
Requires subscribers:delete
Permanent, and there is no undo — call with ?dry_run=true first.
The subscriber row is removed and their engagement trail goes with it: list memberships cascade — INCLUDING any per-list unsubscribe records, so re-creating the same address and re-adding it to a list would mail someone who had left that list — and open/click tracking rows are deleted. The send log is kept for campaign analytics with its email anonymised in the same transaction.
A GLOBAL opt-out survives: the suppression list is keyed by email address, not by subscriber, and deleting the subscriber does not touch it. This is also not a complete GDPR erasure for the person — the address may still appear as a contact or in the inbox; use the account data-erasure flow for that.
Path parameters1
subscriberIdstringrequired1..64 charsQuery parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.curl -X DELETE "https://api.mailneo.co/api/v2/subscribers/{subscriberId}" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": {
"object": "subscriber",
"id": "c0oevquz0o4w5600oyuy8x0oo",
"deleted": true
},
"meta": {
"request_id": "2314bc1e-2414-4db1-a114-b8f82214ba8b",
"dry_run": false
}
}