Subscribers
Manage your subscriber contacts, import lists, and track engagement
Manage your email subscriber contacts. Each subscriber has a profile with engagement tracking, custom fields, and status management.
Subscriber Statuses
| Status | Value | Description |
|---|---|---|
Active | 0 | Subscribed and receiving emails |
Unsubscribed | 1 | Opted out of emails |
Bounced | 2 | Hard bounce recorded |
Complained | 3 | Filed a spam complaint |
Pending | 4 | Reserved: MailLand has no double opt-in flow, so nothing ever sets this status |
optInMethod and the consent* fields on a subscriber are recorded as evidence only: they do not drive any confirmation workflow.
Create a Subscriber
POST https://api.mail.land/v1/marketing/subscribers{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"phone": "+1234567890",
"company": "Acme Corp",
"mailingListId": "ddv6ou6kz4wbto",
"source": "api",
"birthday": "1990-05-14",
"customFields": {
"plan": "enterprise",
"region": "us-west"
}
}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address |
firstName | string | No | First name |
lastName | string | No | Last name |
phone | string | No | Phone number |
company | string | No | Company name |
mailingListId | string | No | Mailing list to add to |
source | string | No | Subscription source (e.g., api, form, import) |
birthday | string | No | YYYY-MM-DD. Read by the DateBased automation trigger |
customFields | object | No | Key-value custom fields |
curl -X POST https://api.mail.land/v1/marketing/subscribers \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"mailingListId": "ddv6ou6kz4wbto",
"source": "api"
}'Response
{
"id": "exz31qoqlf01sj",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"phone": "+1234567890",
"company": "Acme Corp",
"status": "active",
"statusName": "Active",
"source": "api",
"mailingListId": "ddv6ou6kz4wbto",
"mailingListName": "Product Updates",
"customFields": {
"plan": "enterprise",
"region": "us-west"
},
"subscribedAt": "2026-02-17T12:00:00Z",
"unsubscribedAt": null,
"createdAt": "2026-02-17T12:00:00Z",
"updatedAt": "2026-02-17T12:00:00Z"
}List Subscribers
GET https://api.mail.land/v1/marketing/subscribers| Parameter | Type | Default | Description |
|---|---|---|---|
cursor | string | - | Opaque cursor from the previous page's nextCursor. Omit for the first page. |
limit | int | 20 | Items per page |
search | string | - | Search by email or name (not company) |
mailingListId | string | - | Filter by mailing list |
status | int | - | Filter by subscriber status |
sortBy | string | - | Sort field |
sortDesc | bool | false | Sort descending |
curl "https://api.mail.land/v1/marketing/subscribers?limit=20&mailingListId=ddv6ou6kz4wbto" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Get Subscriber Details
GET https://api.mail.land/v1/marketing/subscribers/{id}Returns an extended profile with engagement metrics:
curl https://api.mail.land/v1/marketing/subscribers/exz31qoqlf01sj \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"id": "exz31qoqlf01sj",
"email": "[email protected]",
"firstName": "John",
"lastName": "Doe",
"phone": "+1234567890",
"company": "Acme Corp",
"status": "active",
"source": "api",
"mailingListId": "ddv6ou6kz4wbto",
"mailingListName": "Product Updates",
"customFields": { "plan": "enterprise" },
"birthday": "1990-05-14",
"emailsSent": 50,
"emailsOpened": 25,
"emailsClicked": 10,
"lastEmailSentAt": "2026-02-17T09:30:00Z",
"lastEmailOpenedAt": "2026-02-16T15:20:00Z",
"lastEmailClickedAt": "2026-02-15T11:10:00Z",
"openRate": 50.0,
"clickRate": 40.0,
"subscribedAt": "2026-01-15T08:00:00Z",
"createdAt": "2026-01-15T08:00:00Z",
"updatedAt": "2026-02-17T12:00:00Z"
}Update a Subscriber
PUT https://api.mail.land/v1/marketing/subscribers/{id}{
"firstName": "Jane",
"lastName": "Smith",
"phone": "+1234567890",
"company": "TechCorp",
"mailingListId": "ddv6ou6kz4wbto",
"customFields": { "plan": "pro" },
"birthday": "1990-05-14",
"status": "Active"
}PUT replaces the whole profile, it does not merge. firstName, lastName, phone, company and mailingListId are all overwritten with null (or list removal) when omitted, not left unchanged. Omitting mailingListId removes the subscriber's list membership. Send every field you want to keep, including mailingListId. customFields and status are left unchanged when omitted. birthday follows its own presence rule: omit the field to leave it unchanged, send "birthday": null to clear it, or send a YYYY-MM-DD value to set it, an empty string ("") is refused with 400. If the recipient already has a contact that is in no list, the membership you remove is kept only as history: it no longer appears in contacts, and its unsubscribes, consent records and campaign history are preserved.
curl -X PUT https://api.mail.land/v1/marketing/subscribers/exz31qoqlf01sj \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Smith",
"company": "TechCorp",
"mailingListId": "ddv6ou6kz4wbto",
"customFields": { "plan": "pro" },
"status": "Active"
}'Unsubscribe a Subscriber
POST https://api.mail.land/v1/marketing/subscribers/{id}/unsubscribeRecords an opt-out scoped to the subscriber's list, or to all marketing sends if the subscriber has no list, and sets the subscriber status to Unsubscribed with an unsubscribedAt timestamp. Transactional mail is not affected by this call.
curl -X POST https://api.mail.land/v1/marketing/subscribers/exz31qoqlf01sj/unsubscribe \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Delete a Subscriber
DELETE https://api.mail.land/v1/marketing/subscribers/{id}Permanently removes the subscriber and their engagement data. Opt-outs and suppressions are kept, so re-adding the same address does not re-subscribe it.
curl -X DELETE https://api.mail.land/v1/marketing/subscribers/exz31qoqlf01sj \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."Import Subscribers
POST https://api.mail.land/v1/marketing/subscribers/importImport subscribers from CSV content:
{
"mailingListId": "ddv6ou6kz4wbto",
"csvContent": "email,firstName,lastName\n[email protected],John,Doe\n[email protected],Jane,Smith",
"source": "csv_import",
"updateExisting": true
}| Field | Type | Required | Description |
|---|---|---|---|
mailingListId | string | No | Target mailing list |
csvContent | string | Yes | CSV data: see the format callout below, the header row is not used as a header |
source | string | No | Import source label |
updateExisting | bool | No | Update existing subscribers (default: false) |
The first row is treated as a header and skipped, regardless of its contents: it is never read to map columns 1–3 to fields. Columns are positional: column 1 is always email, column 2 is always firstName, column 3 is always lastName; any other column is ignored except one headed birthday (case-insensitive, any position after column 1), which is read by its header name, not its position. Its value must be YYYY-MM-DD; an invalid value is reported per row in errors and that contact is still imported, just without a birthday. A file with no birthday header behaves exactly as before. Values are split on plain commas, so a value containing a comma inside quotes is not supported and will be split incorrectly. The response also adds importedNewMembershipForExistingRecipient, updatedMembership, alreadyMember, suppressed and skippedDuplicateInFile counters beyond what is shown below.
curl -X POST https://api.mail.land/v1/marketing/subscribers/import \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{
"mailingListId": "ddv6ou6kz4wbto",
"csvContent": "email,firstName,lastName\[email protected],John,Doe\[email protected],Jane,Smith",
"source": "csv_import",
"updateExisting": true
}'{
"imported": 50,
"updated": 10,
"skipped": 5,
"errors": [
"Invalid email: not-an-email",
"Could not resolve a recipient identity for: [email protected]"
]
}Imported subscribers do trigger the SubscriberJoins automation, for every membership the
import actually commits: the same way a single POST /v1/marketing/subscribers create does. A
contact already on the target list, an in-file repeat, or a row lost to a (TenantId, Email)
conflict is not enrolled. If the target list has an active SubscriberJoins automation (or the
tenant has one that is not list-bound), importing a file of new contacts now sends that automation to
every one of them, size the import accordingly. Suppression is still enforced: enrolling never
bypasses a suppressed recipient.
Export Subscribers
GET https://api.mail.land/v1/marketing/subscribers/export| Parameter | Type | Description |
|---|---|---|
mailingListId | string | Filter by mailing list |
status | int | Filter by status |
Returns a CSV file download with filename subscribers_yyyyMMdd_HHmmss.csv.
curl "https://api.mail.land/v1/marketing/subscribers/export?mailingListId=ddv6ou6kz4wbto" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-o subscribers_export.csvSubscriber Stats
GET https://api.mail.land/v1/marketing/subscribers/stats| Parameter | Type | Description |
|---|---|---|
mailingListId | string | Filter by mailing list |
curl "https://api.mail.land/v1/marketing/subscribers/stats?mailingListId=ddv6ou6kz4wbto" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"total": 1500,
"active": 1200,
"unsubscribed": 200,
"bounced": 80,
"complained": 10,
"pending": 10
}Subscriber Count
GET https://api.mail.land/v1/marketing/subscribers/count| Parameter | Type | Description |
|---|---|---|
mailingListId | string | Filter by mailing list |
curl "https://api.mail.land/v1/marketing/subscribers/count?mailingListId=ddv6ou6kz4wbto" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"count": 1500,
"mailingListId": "ddv6ou6kz4wbto"
}Subscriber Growth
GET https://api.mail.land/v1/marketing/subscribers/growth| Parameter | Type | Default | Description |
|---|---|---|---|
days | int | 30 | Lookback period (max 90) |
mailingListId | string | - | Filter by mailing list |
curl "https://api.mail.land/v1/marketing/subscribers/growth?days=30&mailingListId=ddv6ou6kz4wbto" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."{
"dataPoints": [
{
"date": "2026-02-17",
"newSubscribers": 50,
"unsubscribes": 5,
"cumulativeTotal": 1500
}
],
"totalNew": 500,
"totalUnsubscribes": 50,
"netGrowth": 450,
"currentTotal": 1500
}Error Responses
| Status | Code | Description |
|---|---|---|
400 | INVALID_EMAIL_FORMAT | Invalid email format |
400 | INVALID_REQUEST | Subscriber email already exists |
403 | FORBIDDEN | Insufficient permissions |
404 | RESOURCE_NOT_FOUND | Subscriber not found |