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
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
AddgroupBy, 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).
{ count, groups, otherCount, range, truncated, coveredFrom? }:
groupsholds the largest 200 groups, largest first. Each is{ key, bucket?, count }:keymaps eachgroupByfield to its value (nullwhen the event has none), andbucketis the UTC period start.otherCountsums the groups not listed.- One count reads the newest 20,000 events in the window. A busier window answers
truncated: true, andcoveredFrommarks where the counted events begin. Narrowfrom/toor addeventTypeto count all of it.
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 foropenProfile 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.
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.
Related
GET /v1/analytics/eventsin the Public API v1 reference (sidebar): full schema + filters.- Reading analytics: the dashboard view of the same data.
- Pagination: cursor semantics.
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:- Self-Service Tools
- Talk to Our Team
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.