Skip to main content
GET /v1/analytics/events is a unified event feed across four domains: email, automation, trigger, and inbound. Filter by recipient to get one contact’s complete engagement timeline, or by eventType / automationId / sendId to slice the whole brand. Requires the emails scope. Every delivery event attaches to the sendId it came from, so you can also scope the feed to a single send: pass ?sendId= here, or read that send with GET /v1/sends/{sendId}?include=events for a bounded first page on the row itself. See Monitor campaign sends. When from/to are omitted the API defaults to the last 7 days. The response carries { data, pagination, range }.

One Contact’s Timeline

Export a Full Timeline

The SDK auto-pager walks every page so you never touch the cursor:

Filter by Event Type or Automation

The filter is recipient; the row field it matches is recipientEmail. Each EventRow carries domain, eventType, recipientEmail?, sendId?, emailId?/emailName?, automationId?/automationName?, nodeId?, mode? (live | test), and a human summary?.

Count Events Instead of Listing Them

Add groupBy, bucket, or both to count the email events the same filters list instead of returning them. groupBy takes one or two comma-separated fields: eventType, emailId, automationId, sendId, source, link, recipientDomain, unsubscribeReason. bucket takes day, week or month (UTC; weeks start Monday).
The answer is { count, groups, otherCount, range, truncated, coveredFrom? }:
  • groups holds the largest 200 groups, largest first. Each is { key, bucket?, count }: key maps each groupBy field to its value (null when the event has none), and bucket is the UTC period start.
  • otherCount sums the groups not listed.
  • One count reads the newest 20,000 events in the window. A busier window answers truncated: true, and coveredFrom marks where the counted events begin. Narrow from/to or add eventType to count all of it.
A count takes no cursor or automationRunId (400), and limit is ignored. It counts events, not unique recipients, and an unsubscribe counts once per reason it gave. For unique opened and clicked recipients, use GET /v1/analytics/overview. Over MCP, get_email_analytics with report.kind: "events" takes the same groupBy and bucket.

The Open-Time Profile

Brew keeps one open-time profile per contact: when in the day they open email. Ask a contact read for openProfile to get it. Intelligent send doesn’t read this profile. It times each contact from their human opens and clicks directly.
openProfile is null until Brew has recorded an open for the contact. Otherwise it holds: The last three appear only once there is enough history to name a best time. The profile counts opens as the mail provider reports them. Privacy proxies and security scanners that fetch images on delivery look like opens here, and nothing in the profile tells them apart, so don’t use it for bot detection. The event feed does classify each open: GET /v1/analytics/events leaves machine-classified opens out unless you pass includeMachineOpens=true.

For a Page of Contacts

POST /v1/contacts/search takes include: ["openProfile"] in its body and attaches the profile to each returned contact. Each row costs a profile read, so a page holds at most 10 contacts, whatever limit asks; pagination.limit reports the size read. count: true can’t be combined with it (400 INVALID_REQUEST). Over MCP, search_contacts takes the same include.
Either read needs the emails scope as well as contacts, because the profile is engagement data. A contacts-only key gets 403 INSUFFICIENT_PERMISSIONS when it asks for openProfile, and reads contacts without it as before.

Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

Ask ChatGPT or Claude

Click here to open the Copy page menu at the top of this page, then send the page to ChatGPT, Claude, or your editor.