Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DataTalksClub/datamailer/llms.txt

Use this file to discover all available pages before exploring further.

GET /api/contacts/{contact_id}/history gives your application a full scoped view of a contact’s email activity: every campaign they were included in, every transactional message sent to them, and every email event attached to their contact record. Results are scoped to the authenticated client’s audience so that one client cannot read another client’s send history for the same contact.

Endpoint

GET /api/contacts/{contact_id}/history

Authentication

Requires a Bearer token for the client whose slug is passed as the client query parameter.
Authorization: Bearer <client-api-key>

Path parameters

contact_id
integer
required
The internal Datamailer contact ID. Must be a contact that has a client-scoped subscription record in the specified audience.

Query parameters

audience
string
required
Slug of the audience to scope campaign recipient history to.
client
string
required
Slug of the authenticated client. Scopes both campaign and transactional history.
limit
integer
default:"50"
Maximum number of email events to return per page. Must be between 1 and 100. Campaign recipients and transactional messages are also independently capped at limit rows — they are not paginated separately.
cursor
integer
Email event ID to paginate from. Pass the value of next_cursor from the previous response to retrieve the next page of events. Omit for the first page.

Response

contact_id
integer
Internal Datamailer contact ID.
email
string
Normalized (lowercased) email address of the contact.
audience
string
Slug of the audience the history is scoped to.
client
string
Slug of the client the history is scoped to.
campaign_recipients
object[]
Campaign recipient records for this contact in campaigns belonging to the specified audience and client, ordered by created_at descending. Capped at limit rows.
transactional_messages
object[]
Transactional messages sent to this contact by the authenticated client, ordered by created_at descending. Capped at limit rows. Not audience-scoped — includes all transactional sends from this client to this contact.
events
object[]
Email events for this contact scoped to the client and audience, ordered by event id descending. Paginated via cursor and next_cursor.
next_cursor
string | null
The event ID to pass as the cursor query parameter to retrieve the next page of events. null when there are no further pages.

Example

Request

curl "https://datamailer.example.com/api/contacts/42/history?audience=dtc-courses&client=dtc-courses&limit=25" \
  -H "Authorization: Bearer dm_dtccourses_demo_transactional_email_key"

Response

{
  "contact_id": 42,
  "email": "learner@example.com",
  "audience": "dtc-courses",
  "client": "dtc-courses",
  "campaign_recipients": [
    {
      "type": "campaign_recipient",
      "id": 1001,
      "created_at": "2024-09-10T08:00:00Z",
      "campaign": {
        "id": 7,
        "subject": "ML Zoomcamp — Week 2 kickoff",
        "audience": "dtc-courses",
        "client": "dtc-courses"
      },
      "email": "learner@example.com",
      "status": "sent",
      "skip_reason": null,
      "sent_at": "2024-09-10T08:05:00Z",
      "delivered_at": "2024-09-10T08:05:12Z",
      "first_opened_at": "2024-09-10T09:14:00Z",
      "first_clicked_at": null,
      "open_count": 2,
      "click_count": 0,
      "last_error": ""
    }
  ],
  "transactional_messages": [
    {
      "type": "transactional_message",
      "id": 305,
      "created_at": "2024-09-01T10:00:00Z",
      "client": "dtc-courses",
      "email": "learner@example.com",
      "template_key": "registration-welcome",
      "status": "sent",
      "subject": "Welcome to ML Zoomcamp!",
      "sent_at": "2024-09-01T10:00:05Z",
      "delivered_at": "2024-09-01T10:00:18Z",
      "first_opened_at": "2024-09-01T10:30:00Z",
      "first_clicked_at": "2024-09-01T10:31:45Z",
      "open_count": 3,
      "click_count": 1,
      "last_error": ""
    }
  ],
  "events": [
    {
      "type": "email_event",
      "id": 8842,
      "created_at": "2024-09-10T09:14:00Z",
      "event_type": "open",
      "client": "dtc-courses",
      "audience": "dtc-courses",
      "campaign_id": 7,
      "campaign_recipient_id": 1001,
      "transactional_message_id": null,
      "metadata": {}
    },
    {
      "type": "email_event",
      "id": 8101,
      "created_at": "2024-09-01T10:31:45Z",
      "event_type": "click",
      "client": "dtc-courses",
      "audience": null,
      "campaign_id": null,
      "campaign_recipient_id": null,
      "transactional_message_id": 305,
      "metadata": {}
    }
  ],
  "next_cursor": null
}

Paginating events

Pass next_cursor as the cursor parameter on the next request to retrieve older events:
curl "https://datamailer.example.com/api/contacts/42/history?audience=dtc-courses&client=dtc-courses&limit=25&cursor=8101" \
  -H "Authorization: Bearer dm_dtccourses_demo_transactional_email_key"
Privacy: secret tracking tokens, unsubscribe token hashes, and delivery link tokens stored on CampaignRecipient and TransactionalMessage records are never included in the history response. Only safe display fields are returned.
Scope: campaign_recipients is filtered to campaigns whose audience and client match the query parameters. transactional_messages is filtered by client only — transactional sends are not audience-scoped. events includes events where client matches or is null, and audience matches or is null, keeping provider webhook events (which may lack audience context) visible.

Error codes

FieldError codeMeaning
contact_idnot_foundNo contact with that ID, or no client subscription in this audience. Returns HTTP 404.
audiencerequiredaudience was missing or blank.
audiencenot_foundNo audience with that slug in the client’s organization.
clientrequiredclient was missing or blank.
clientforbiddenclient does not match the authenticated client. Returns HTTP 403.
limitmust_be_integerlimit query parameter could not be parsed as an integer.
limitmust_be_between_1_and_100limit is outside the allowed range of 1–100.
cursormust_be_integercursor query parameter could not be parsed as an integer.
cursormust_be_positivecursor is less than 1.

Build docs developers (and LLMs) love