Aller à la documentation
API

Suivi

Chaque opération de ce groupe : ce qu'elle accepte, ce qu'elle retourne et les erreurs qu'elle peut renvoyer.

Opérations

Who read the mail and what they followed. Off unless the workspace turned it on, per user and per send.

Every count here is a floor rather than a total, and that is the mechanism rather than a defect: an open is inferred from a mail client fetching an image, so a reader whose client blocks images reads without being counted, and Gmail fetches the image once through its own proxy and serves every later view from cache. Mail from one OpenEmail mailbox to another never reports an open at all. OpenEmail strips 1×1 images out of what its own users read, and it makes no exception for its own pixel. A message with no opens has not been shown to be unread; a click is the stronger evidence, because links go unfollowed far less often than images go unloaded.

GET/emails/{id}/tracking

How a message was read

Portéesemails:readLit

The same document /tracking/{id} serves, reached from the id a caller already holds. A message that was never tracked is a 404 here rather than an empty report, because "we were not recording" and "nobody opened it" are different answers and a client that renders them the same way makes a claim about a reader on no evidence.

Requires the emails:read scope.

Paramètres de chemin

idstringObligatoire

The msg_ send id returned by send.

Retourne

The full report, with per-recipient and per-link detail.

Erreurs

404

No such message, or nothing was tracked for it.

Les erreurs que toute opération peut renvoyer400401403422500Catalogue des erreurs

Aussi disponible dans

SDK
emails.getTracking()
CLI
openemail emails get-tracking
MCP
getEmailTracking

GET/tracking

List tracked messages

Portéesemails:readLit

Every message the workspace tracked in the window, newest first, one page at a time, not only the ones sent through this API. The composer, the MCP tools and the assistant mostly send through the mailbox agent, which writes the send record itself and never links it to the tracking row, so those messages carry no counts on /emails and only this list holds them.

opened=false means tracked and not opened. Messages that carried no pixel are absent from this list entirely and never appear as a zero. Follow nextCursor with the same filters to reach every tracked message in the window.

Requires the emails:read scope.

Paramètres de requête

openedboolean

Omit for both. false narrows to tracked-and-unopened.

clickedboolean

Omit for both. true keeps messages with a counted click and false keeps those without.

daysinteger

How far back to look. The window starts at the beginning of that day rather than at this time of day, and ends now.

Au moins 1Au plus 365Par défaut30
minutesinteger

The window in minutes, which wins over days when both are sent. A whole number of days cannot say "the last hour", which is the report worth having while a send is going out.

Au moins 1Au plus 527040
grainstring

Only used to floor the start of the window, so this list can cover the same window as /tracking/stats read at the same grain. It shapes nothing in the response.

L'un de"minute""hour""day"Par défaut"day"
limitinteger

Rows per page, 1 to 200.

Au moins 1Au plus 200Par défaut50
cursorstring

A tmsg_ tracking id. Keyset, not offset: pass the previous page's nextCursor. One that names nothing in this list is a 400 invalid_cursor.

Retourne

A page of tracked messages, newest first.

Erreurs

Les erreurs que toute opération peut renvoyer400401403404422500Catalogue des erreurs

Aussi disponible dans

SDK
tracking.list()tracking.listAll()tracking.iterate()
CLI
openemail tracking list
MCP
listTrackedEmails

GET/tracking/stats

Engagement over a window

Portéesemails:readLit

The numbers behind an engagement panel, in one request. Rates are over tracked messages and count distinct messages; the totals count hits. Read the field descriptions before charting any of it. The two are easy to mix and the result is an open rate above 100%.

A key is the workspace's own authority and sees the whole workspace. The per-member address grants that narrow this in the app belong to a session, and a key has none.

Requires the emails:read scope.

Paramètres de requête

daysinteger

How far back to look. The window starts at the beginning of that day in the offset you asked for, so the oldest byDay bucket is a whole one, and ends now, so the newest is partial.

Au moins 1Au plus 365Par défaut30
minutesinteger

The window in minutes, which wins over days when both are sent. A whole number of days cannot say "the last hour", which is the report worth having while a send is going out.

Au moins 1Au plus 527040
grainstring

How wide one byDay bucket is. An hour of mail bucketed by day is a single entry, so a window shorter than a day is only worth asking for alongside a grain that can describe it. The bucket keys change shape with it: YYYY-MM-DD for a day, YYYY-MM-DDTHH for an hour, YYYY-MM-DDTHH:MM for a minute.

L'un de"minute""hour""day"Par défaut"day"
offsetMinutesinteger

Minutes east of UTC to bucket byDay in, so days break where the reader's day breaks. Left at zero, this morning's mail lands on yesterday's bar for anyone west of UTC.

Au moins -840Au plus 840Par défaut0

Retourne

The window, summarised. byDay is sparse.

Erreurs

Les erreurs que toute opération peut renvoyer400401403404422500Catalogue des erreurs

Aussi disponible dans

SDK
tracking.getStats()
CLI
openemail tracking get-stats
MCP
getEngagementStats

GET/tracking/{id}

Retrieve one message's tracking

Portéesemails:readLit

The whole report: per-recipient counts where the transport could attribute them, and every rewritten link with its clicks.

Requires the emails:read scope.

Paramètres de chemin

idstringObligatoire

A tmsg_ tracking id or the msg_ send id the message went out as. Both work, because a caller who sent through this API holds the second and has no reason to know the first exists.

Retourne

The report.

Erreurs

404

Nothing was tracked for that message. Not the same as nobody having opened it.

Les erreurs que toute opération peut renvoyer400401403422500Catalogue des erreurs

Aussi disponible dans

SDK
tracking.get()
CLI
openemail tracking get
MCP
listTrackedEmails

GET/tracking/{id}/opens

The individual opens

Portéesemails:readLit

The raw hits behind openCount, newest first, one page at a time. Every stored hit is reachable by following nextCursor.

A msg_ send id is resolved first, and one that names nothing is a 404 rather than an empty list. An empty list reads as "nobody opened it", which is the one answer this endpoint must never give by accident. A tmsg_ is taken as given and costs no lookup, so an id that never existed, or one belonging to another workspace, does come back here as an empty list. Only ids this API handed you can be read as an answer about a reader.

Requires the emails:read scope.

Paramètres de chemin

idstringObligatoire

A tmsg_ tracking id or the msg_ send id the message went out as. Both work, because a caller who sent through this API holds the second and has no reason to know the first exists.

Paramètres de requête

includeMachineboolean

Include the hits that did not count: Apple Mail Privacy Protection, corporate scanners, and the repeats collapsed by the thirty-second window. Off by default, and that default is the honest one. Those fetches are stored so the gap between the raw and counted totals stays inspectable, not because anybody read anything. Gmail's image proxy is not among them and is never hidden by this. Send true or false: ?includeMachine=false means false here, which is worth saying because the obvious coercion would make it true.

Par défautfalse
limitinteger

Rows per page, 1 to 200.

Au moins 1Au plus 200Par défaut50
cursorstring

An opn_ open id from this message. Keyset, not offset: pass the previous page's nextCursor. One that names nothing in this list is a 400 invalid_cursor.

Retourne

A page of opens, newest first.

Erreurs

Les erreurs que toute opération peut renvoyer400401403404422500Catalogue des erreurs

Aussi disponible dans

SDK
tracking.listOpens()tracking.listAllOpens()tracking.iterateOpens()
CLI
openemail tracking list-opens
MCP
getEmailTracking

GET/tracking/{id}/clicks

The individual clicks

Portéesemails:readLit

As the opens, plus which link was followed, one page at a time. Only links in the new part of the body were rewritten, so nothing here can be a click on quoted history.

Requires the emails:read scope.

Paramètres de chemin

idstringObligatoire

A tmsg_ tracking id or the msg_ send id the message went out as. Both work, because a caller who sent through this API holds the second and has no reason to know the first exists.

Paramètres de requête

includeMachineboolean

Include the hits that did not count: Apple Mail Privacy Protection, corporate scanners, and the repeats collapsed by the thirty-second window. Off by default, and that default is the honest one. Those fetches are stored so the gap between the raw and counted totals stays inspectable, not because anybody read anything. Gmail's image proxy is not among them and is never hidden by this. Send true or false: ?includeMachine=false means false here, which is worth saying because the obvious coercion would make it true.

Par défautfalse
limitinteger

Rows per page, 1 to 200.

Au moins 1Au plus 200Par défaut50
cursorstring

A clk_ click id from this message. Keyset, not offset: pass the previous page's nextCursor. One that names nothing in this list is a 400 invalid_cursor.

Retourne

A page of clicks, newest first.

Erreurs

Les erreurs que toute opération peut renvoyer400401403404422500Catalogue des erreurs

Aussi disponible dans

SDK
tracking.listClicks()tracking.listAllClicks()tracking.iterateClicks()
CLI
openemail tracking list-clicks
MCP
getEmailTracking

Objets

Clickobject

objectstring
L'un de"click"
idstring
trackedMessageIdstring
recipientstring

Null for the same reason recipients[].email is: shared bytes, unknowable reader.

Peut être null
kindstring

human looked like somebody reading. proxy is an image proxy, Gmail's above all: a genuine reading by a reader we cannot see, counted once and then cached out of our sight. machine is a scanner or Apple Mail Privacy Protection, which fetches on delivery and means only that the message arrived.

L'un de"human""proxy""machine"
countedboolean

Whether this hit moved the numbers. False for every machine hit, and false again for a hit landing within thirty seconds of the last counted one on the same copy. A preview pane redrawing is the same reading rather than a second one. includeMachine filters on this field and not on kind, so it is what hides those collapsed repeats as well.

clientstring
Peut être null
devicestring
Peut être null
osstring

Read out of the User-Agent, which is a claim rather than a fact and is absent altogether on plenty of hits.

Peut être null
countrystring

What the edge already knew about the request, so this is as precise as the location will ever be, and on a proxied hit it is the proxy's country rather than the reader's. There is no ip field on this resource, but the address is not discarded: it is written into the event log for the message, readable at GET /emails/{id}/events, and into the email.opened and email.clicked webhook payloads.

Peut être null
regionstring
Peut être null
citystring
Peut être null
createdAtstring
Formatdate-time
linkIdstring
urlstring

The destination that was followed.

ClickListobject

objectstring
L'un de"list"
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

The id of the last row on this page, or null on the last page.

Peut être null

Openobject

objectstring
L'un de"open"
idstring
trackedMessageIdstring
recipientstring

Null for the same reason recipients[].email is: shared bytes, unknowable reader.

Peut être null
kindstring

human looked like somebody reading. proxy is an image proxy, Gmail's above all: a genuine reading by a reader we cannot see, counted once and then cached out of our sight. machine is a scanner or Apple Mail Privacy Protection, which fetches on delivery and means only that the message arrived.

L'un de"human""proxy""machine"
countedboolean

Whether this hit moved the numbers. False for every machine hit, and false again for a hit landing within thirty seconds of the last counted one on the same copy. A preview pane redrawing is the same reading rather than a second one. includeMachine filters on this field and not on kind, so it is what hides those collapsed repeats as well.

clientstring
Peut être null
devicestring
Peut être null
osstring

Read out of the User-Agent, which is a claim rather than a fact and is absent altogether on plenty of hits.

Peut être null
countrystring

What the edge already knew about the request, so this is as precise as the location will ever be, and on a proxied hit it is the proxy's country rather than the reader's. There is no ip field on this resource, but the address is not discarded: it is written into the event log for the message, readable at GET /emails/{id}/events, and into the email.opened and email.clicked webhook payloads.

Peut être null
regionstring
Peut être null
citystring
Peut être null
createdAtstring
Formatdate-time

OpenListobject

objectstring
L'un de"list"
dataOpen[]
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

The id of the last row on this page, or null on the last page.

Peut être null

Trackingobject

Every /tracking endpoint returns this whole, its list included. It arrives trimmed in exactly one place, under tracking on a GET /emails row, where it is the counts half only: opens, clicks, opened, clicked, openCount, clickCount and firstOpenAt. A page of fifty sends each carrying its recipients and its links is a report nobody asked to have expanded. The trimmed form has no id on it either, so /emails/{id}/tracking rather than /tracking/{id} is the way back to the rest of it.

objectstring

Present when the report is the whole response body. Absent under an email's tracking field, which is part of that email rather than a resource in its own right.

L'un de"tracking"
idstring

The tracking record, tmsg_ + 24 hex. Not the message id and not the send id.

sendIdstring

The msg_ this went out as, when the send service handled it. Null for mail the mailbox agent sent on its own behalf, which is most composer, MCP and assistant traffic. Those messages do get a send record, but nothing links this tracking row to it. Tracking covers the mailbox rather than only the traffic that came through this API.

Peut être null
threadIdstring
Peut être null
messageIdstring

RFC 5322 Message-ID. Not a correlation key. The header is rewritten on the way out. Correlate on id.

Peut être null
subjectstring
Peut être null
fromstring
sourcestring
L'un de"api""oauth""composer""mcp""ai""form"
sentAtstring
Peut être nullFormatdate-time
opensboolean

What was APPLIED to this message, resolved when it was sent from the setting of the address it was sent from (its own, else its domain catch-all's, else off, while a broadcast copy is on unless the broadcast or its address turned it off) and any per-send override, not what is switched on now. Turning tracking on today does not make yesterday's mail start reporting, and a report that implied otherwise would read as "nobody opened it".

clicksboolean

As opens, for link rewriting. The two are independent switches.

openedboolean
clickedboolean
attributableboolean

Whether every reading on this message can be pinned to a named recipient. False as soon as an unattributed copy has activity of its own, which is what happens whenever one body went to the whole list rather than a separate one per person. This is the flag that decides whether "Bob has not opened it" is a sentence a client is entitled to write, or whether all it may say is that somebody did. recipients carries the same fact one row at a time, and one row at a time is where it gets missed.

openCountinteger

Opens that looked like a person, with repeat fetches within thirty seconds collapsed. A preview pane redrawing is not a second reading. Through Gmail this is a floor and not a total: its proxy fetches the image once and caches it, so later readings never reach us.

clickCountinteger

Counted clicks. Stronger evidence than an open, and worth weighting as such: images are blocked far more often than links go unfollowed, so a message with clicks and no opens was certainly read.

openCountRawinteger

Every open hit, the automated ones included. openCountRaw - openCount is everything that was filtered out: Apple Mail Privacy Protection and corporate link scanners, which fetch on delivery whether or not a person ever looks, and alongside them the repeat fetches collapsed by the thirty-second window. Both are recorded and neither is counted, because discarding them outright would leave a gap in the log that nothing could explain. Do not read the difference as a machine count on its own. A message reopened twice in a minute lands in it too.

clickCountRawinteger

As openCountRaw, for clicks.

firstOpenAtstring
Peut être nullFormatdate-time
lastOpenAtstring
Peut être nullFormatdate-time
firstClickAtstring
Peut être nullFormatdate-time
lastClickAtstring
Peut être nullFormatdate-time
recipientsobject[]

One entry per tracked copy, which is not always one entry per person. Absent only from the trimmed form on a GET /emails row; every /tracking response carries it, list included.

emailstring

Null where the bytes could not be varied per person: an encrypted message, one too large to rebuild for each recipient, or a fallback carrier that takes the whole recipient list in a single call. The reading is real; which of the recipients did it is not knowable, and the only honest rendering is "someone on this message", never a name chosen out of the list.

Peut être null
kindstring
Peut être nullL'un de"to""cc""bcc"
attributedboolean

False on exactly the rows described above. Branch on this rather than on email being a string, and show nothing where it is false: attributing an unattributed open to a named recipient invents evidence about a specific person.

openCountinteger
clickCountinteger
firstOpenAtstring
Peut être nullFormatdate-time
lastOpenAtstring
Peut être nullFormatdate-time
firstClickAtstring
Peut être nullFormatdate-time
lastClickAtstring
Peut être nullFormatdate-time
linksobject[]

The rewritten links, in the order they appeared in the message. Only links in the new part of the body are here: the quoted history under a reply belongs to whoever wrote it, and routing their URLs through our redirector would both rewrite their message and record the recipient "clicking" something we did not put there. Repeated destinations share one entry, because a campaign page linked from a header image, a button and a footer is one question asked three times. Absent only from the trimmed form on a GET /emails row.

idstring
urlstring

Where it actually goes: the original href.

labelstring

The text the link read as in the message, where it had any. A bare URL rarely tells the sender which of five links somebody followed.

Peut être null
clickCountinteger
clickCountRawinteger

TrackingListobject

objectstring
L'un de"list"
hasMoreboolean

True when another page follows. Pass nextCursor back as cursor to read it.

nextCursorstring

The id of the last row on this page, or null on the last page.

Peut être null

TrackingStatsobject

objectstring
L'un de"tracking_stats"
trackedinteger

Messages in the window that carried a pixel or a rewritten link.

openedinteger

Of those, how many a person opened at least once. Distinct MESSAGES, not hits. A message opened five times is one opened message, and conflating the two is how open rates above 100% get published.

clickedinteger
trackedForOpensinteger

The denominator of openRate. Tracking is two switches rather than one, so this is the messages that actually carried a pixel, not every tracked message.

trackedForClicksinteger

The denominator of clickRate, and the reason it is published. A message with no links is never click-tracked, so counting it against the click rate makes that rate a measure of how much of your mail contains a link. Most mail is a reply with no links, so the difference from tracked is usually large.

openRatenumber

A percentage of trackedForOpens, deliberately not of everything sent. A workspace that tracks one message in ten has an open rate for those ten; dividing by all its mail produces a number that falls every time somebody sends an untracked reply, which is not a fact about how anyone is reading.

clickRatenumber

A percentage of trackedForClicks, as openRate is of trackedForOpens.

totalOpensinteger

Hits rather than messages. Labelled as a total because it is the easy one to misread.

totalClicksinteger
machineOpensinteger

Hits excluded from every number above: the sum of openCountRaw - openCount, so Apple Mail Privacy Protection and corporate scanners together with the repeats the thirty-second window collapsed. Reported rather than hidden, because a reader who cannot see how much of the traffic was machinery has no way to judge the rest of the panel. Gmail's image proxy is not in this number: that fetch is a real person displaying the message, and it is counted.

medianTimeToOpenSecondsinteger

Median seconds from send to first counted open, over the messages that were opened at all. Null when none were. A median over an empty set is not zero.

Peut être null
byDayobject[]

SPARSE. A day on which nothing was sent has no entry rather than a row of zeroes, so a chart has to fill the gaps itself. Days break at offsetMinutes east of UTC, so that they break where the reader's day does rather than where the database's does.

daystring

The bucket, in the requested offset, written in the shape grain asked for: YYYY-MM-DD for a day, YYYY-MM-DDTHH for an hour, YYYY-MM-DDTHH:MM for a minute. The field keeps its name at every width because it is the bucket key whatever the bucket is.

sentinteger
openedinteger
clickedinteger
topLinksobject[]
urlstring
labelstring
Peut être null
clickCountinteger
clientsobject[]

Mail clients by counted opens, as far as a User-Agent can be trusted to name one.

clientstring
countinteger
countriesobject[]
countrystring
countinteger