Contacts
What a contact's page shows, what can be changed there, and the API calls that read and edit the same record.
Every address in Contacts opens the contact’s page. So does each person in a campaign’s results, and Open contact in the preview’s subscriber picker.
What the page shows
| Part | What it holds |
|---|---|
| Header | The address, first name and the day the contact was added |
| Suppressed | Shown when no publication may email the address: why (bounced, marked as spam, unsubscribed everywhere, or by hand), where that came from, and when |
| Publications | Each publication the contact is on: subscribed or not, how they joined (signed up, imported, or moved from another tool), the source and date of that consent, tags, and who ended the subscription |
| Fields | first_name and every attribute, by the name a merge tag uses: city fills {{city}} |
| Emails | The latest 50 emails sent to the contact, newest first, with what happened to each |
Who ended a subscription is one of:
| Value | Meaning |
|---|---|
link |
The unsubscribe link in an email, or the mailbox’s own unsubscribe button |
import |
A CSV import or an API call marked them unsubscribed |
profile:<address> |
Someone in the workspace, on the contact’s page |
| none | The subscription ended before this was recorded |
What happened to an email is the worst of what Amazon reported: rejected, marked as spam, bounced, unsubscribed through it, a temporary bounce, delivered, or sent and not reported yet. An email can also be queued, skipped (the person unsubscribed or was suppressed before their turn) or not sent. Test sends go to the people who write the campaign, so they are not listed. Opens and clicks are not tracked.
What can be changed
Admins and members can change these; contributors see the page but not the buttons.
- Fields. Field names are lowercase letters, numbers and
_, starting with a letter, up to 40 characters. Values are up to 200 characters; a contact has at most 100 fields. A saved field keeps its name, because campaigns may already use it; remove it and add a new one to rename it. If someone else saved the contact after you opened it, your save is refused so their change is not lost. - Unsubscribe from one publication. It cannot be undone from Pneumail: only the person can sign up again.
- Suppress the address, so no publication in the workspace emails it. Suppressions are only added here.
Reading and editing a contact with the API
{contact} is the contact’s id (ct_...) or address. The id keeps the address out of request logs.
GET /api/v1/contacts/{contact}
Authorization: Bearer <key with contacts:read>
{
"ok": true,
"contact": {
"id": "ct_...",
"email": "ana@example.org",
"firstName": "Ana",
"attributes": { "city": "Tacoma", "seats": 3 },
"createdAt": "2026-03-14T10:00:00.000Z",
"version": "1792310400000000",
"subs": [
{ "publicationId": "pub_...", "status": "unsubscribed", "consent": "signup", "source": "form:/updates",
"consentedAt": "2026-03-14T10:00:00.000Z", "unsubscribedAt": "2026-10-02T09:00:00.000Z",
"unsubscribedBy": "profile", "tags": ["resident"] }
],
"suppression": null,
"sends": [
{ "id": "snd_...", "kind": "campaign", "campaignId": "cmp_...", "subject": "October news", "publicationId": "pub_...",
"status": "sent", "at": "2026-10-21T10:00:00.000Z", "deliveredAt": "2026-10-21T10:00:02.000Z", "bounce": null,
"complainedAt": null, "unsubscribedAt": null, "rejected": false, "error": null }
],
"moreSends": false
}
}
The API says profile where the page names the person who unsubscribed someone, and Contact page for a
suppression added there, so a key never learns who in the workspace did it.
PATCH /api/v1/contacts/{contact}
Authorization: Bearer <key with contacts:write>
{ "version": "1792310400000000", "firstName": "Ana", "attributes": { "city": "Tacoma", "seats": 4 } }
This replaces the first name and every field, with the same rules as the page: send the full set, and a field
left out is removed. version comes from the last GET; if the contact changed since, the answer is 409 and nothing
is saved. An unknown contact is 404.
To unsubscribe someone through the API, import them with status: "unsubscribed". To suppress an address, add it
to /api/v1/suppressions.