Ugrás a dokumentációra
API

Megnyitás- és kattintáskövetés

GET /tracking: elolvasták-e az üzenetet, és mit követtek le róla.

GETapi.openemail.uk/emails/{id}/tracking

Az oldalon lévő 6 hívás bármelyikét lefuttatja a munkaterületén, a saját kulcsával.

Mit rögzítünk

Két független kapcsoló, mindkettő bekapcsolva, hacsak ki nem kapcsolták annál a címnél, amelyről az üzenet megy, vagy az Összes címnél. Az opens egy 1×1-es képet fűz hozzá; a clicks átírja a törzs új részében lévő linkeket. A válasz alatti idézett előzmény valaki más üzenete, azt békén hagyjuk. A küldés a tracking: { opens, clicks } mezővel dönt egyetlen üzenetről (mindkét irányban, tehát a false az, ahogyan egy program elhárítja, amit a cím beállítása előírna), a kihagyott mező pedig annak a címnek a beállítására esik vissza, amelyről küldik, majd az Összes címre, nem pedig egy olyan alapértékre, amelyet ez az API választott a munkaterület nevében.

POST /emails
{    "from": "Acme Billing <[email protected]>",    "to": ["[email protected]"],    "subject": "Your September invoice",    "html": "<p>Invoice attached.</p>",    "tracking": { "opens": true, "clicks": true }  }

Üzenetenként legfeljebb 100 célt írunk át, mindegyiket egyszer. A fejléc képéből, egy gombból és a láblécből hivatkozott ugyanazon URL egyetlen sor, mert egyetlen kérdés, háromszor feltéve. A korlát fölött a maradék linkek pontosan úgy maradnak, ahogy megírták őket: a nem követett link is működik, és az az üzenet, amely csendben elveszíti az utolsó kétszáz linkjét, sokkal rosszabb hiba, mint egy hiányos jelentés.

Az átírt linkek és a pixel alapértelmezetten az OpenEmail API hostjára mutatnak. Ha a küldő domainnek van egyéni követési domainje, amelynek tracking.status értéke active, a domainről induló új levél helyette a https://<követési host>/t/... címet használja, és a PATCH /domains/{id} az, ahol beállíthatsz egyet.

Mindehhez emails:read kell, és nincs külön követési hatókör. Az a hatókör már most is azt jelenti, hogy „elküldött üzenetek és kézbesítési állapotuk olvasása”, márpedig az, hogy valaki megnyitott-e egy üzenetet, a lehető legszó szerintibb kézbesítési állapot.

A végpontok

HívásMit ad vissza
`GET /tracking`Követett üzenetek, a legújabbakkal kezdve. opened, clicked, days (1–365, alapértelmezetten 30), limit (legfeljebb 200).
`GET /tracking/stats`Arányok egy időablakra. days (alapértelmezetten 30) és offsetMinutes, hogy a napok ott törjenek, ahol az olvasó napja.
`GET /tracking/{id}`Egy jelentés. tmsg_ követési azonosítót vagy a küldés által visszaadott msg_ azonosítót vesz át.
`GET /tracking/{id}/opens`Az egyedi lekérések. includeMachine, limit (legfeljebb 200).
`GET /tracking/{id}/clicks`Ugyanaz, soronként linkId és url mezővel.
`GET /emails/{id}/tracking`Ugyanaz a jelentés, a már kezedben lévő küldési azonosítóból.

A logikai értékeket kiírva kell megadni a query stringben: true, false, 1 vagy 0, minden mást elutasítunk. A Boolean("false") igaz, így egy típuskényszerített ?opened=false pontosan az ellenkezőjét adná vissza annak, amit kértek.

Ez azért önálló erőforrás, nem néhány mező az /emails végponton, a lefedettség miatt: az a lista küldési rekordokat tart, a szerkesztő, az MCP eszközök és az asszisztens viszont mind úgy küldenek, hogy nem írnak ilyet. Az arra épülő jelentés az API forgalmadról szólna, nem a postafiókról.

A jelentés

GET /tracking/tmsg_9c1f7b2e4a5d40b8a3e61d2f
{    "object": "tracking",    "id": "tmsg_9c1f7b2e4a5d40b8a3e61d2f",    "sendId": "msg_c5f21cc6bfec4e848caf905b",    "threadId": "thread_2f9b…",    "messageId": "<2598…@acme.com>",    "subject": "Your September invoice",    "from": "[email protected]",    "source": "api",    "sentAt": "2026-08-29T08:19:08.000Z",    "opens": true,    "clicks": true,    "opened": true,    "clicked": true,    "attributable": true,    "openCount": 3,    "openCountRaw": 7,    "clickCount": 1,    "clickCountRaw": 2,    "firstOpenAt": "2026-08-29T09:04:11.000Z",    "lastOpenAt": "2026-08-30T07:42:55.000Z",    "firstClickAt": "2026-08-29T09:05:02.000Z",    "lastClickAt": "2026-08-29T09:05:02.000Z",    "recipients": [      {        "email": "[email protected]",        "kind": "to",        "attributed": true,        "openCount": 3,        "clickCount": 1,        "firstOpenAt": "2026-08-29T09:04:11.000Z",        "lastOpenAt": "2026-08-30T07:42:55.000Z",        "firstClickAt": "2026-08-29T09:05:02.000Z",        "lastClickAt": "2026-08-29T09:05:02.000Z"      }    ],    "links": [      {        "id": "lnk_4f0a1c8d29b74e6fa3c05d17",        "url": "https://acme.com/invoices/42",        "label": "View invoice",        "clickCount": 1,        "clickCountRaw": 2      }    ]  }

Az opens és a clicks az, amit az üzenetre ALKALMAZTUNK; az opened és a clicked az, ami történt. Az openCount az olvasásokat számolja, az openCountRaw a lekéréseket. A különbség, itt négy, a szkennerek és az adatvédelmi proxyk, és azért tartjuk meg, hogy a napló és az összeg közötti rés megvizsgálható legyen, ne megmagyarázatlan. Az attributable az a mező, amelyet el kell olvasni, mielőtt bárkit megneveznél: a hamis azt jelenti, hogy az olvasás egy olyan példányon történt, amely az egész listának ment ki, és onnantól minden egy adott címzettről szóló mondat találgatás.

A source azt a felületet nevezi meg, amely küldte: api az ezen az API-n át küldött üzenetnél, composer mindennél, amit maga az alkalmazás küldött. A sendId a második fajtánál null, és ezért létezik a követési azonosító.

A null email mezőjű, attributed: false sor az, ahová egy személyhez nem köthető olvasás kerül, és a jelentés csak akkor mutat ilyet, ha tényleg érkezett rá olvasás. Az egyetlen címzettes üzeneten egyáltalán nincs ilyen, mert egy törzs és egy címzett ugyanaz az állítás. A több címzettesnél a kiküldés pillanatától van mögötte egy, mert a transzport a feladásig nincs eldöntve, és addig marad ki a jelentésből, amíg nem érkezik rá semmi: a megnevezett címzettek mellett álló örökös „valaki: nem nyitotta meg” olyan sor, amelyet csak félreolvasni lehet. Ahol VAN, ott a megnevezett sorok azok, amelyek nullán állnak, és az attributable értéke false. Az olvasás valódi, az olvasó az üzeneten szereplő emberek egyike, és a „valaki ezen az üzeneten” az egyetlen megjelenítés, amelyet az adat alátámaszt. Soha ne töltsd ki a nevet a címzettlistából.

Arányok egy időablakra

GET /tracking/stats?days=30&offsetMinutes=60
{    "object": "tracking_stats",    "tracked": 128,    "trackedForOpens": 128,    "trackedForClicks": 47,    "opened": 91,    "clicked": 34,    "openRate": 71.1,    "clickRate": 72.3,    "totalOpens": 240,    "totalClicks": 52,    "machineOpens": 173,    "medianTimeToOpenSeconds": 2714,    "byDay": [{ "day": "2026-08-27", "sent": 12, "opened": 9, "clicked": 3 }],    "topLinks": [{ "url": "https://acme.com/pricing", "label": "See pricing", "clickCount": 18 }],    "clients": [{ "client": "Gmail", "count": 96 }],    "countries": [{ "country": "GB", "count": 71 }]  }

Az arányok a KÖVETETT üzenetekre vett százalékok, nem az összes elküldött levélre: annak a munkaterületnek, amely tíz üzenetből egyet követ, azokra a tízre van megnyitási aránya, és ha mindennel osztanánk, amit valaha küldött, az arány minden nem követett válasz után esne. Az ötször megnyitott üzenet EGY megnyitott üzenet. Az arányok üzeneteket számolnak, az összegek találatokat, és a kettő összemosásából születnek a 100% fölötti megnyitási arányok.

A byDay ritka: az a nap, amelyen semmit nem követtünk, hiányzik, nem nulla, ezért ábrázolás előtt töltsd ki a réseket. A napokat az UTC-től keletre offsetMinutes (−840 és 840 között) szerint vödrözzük, hogy ott törjenek, ahol az olvasó napja. A medianTimeToOpenSeconds medián, nem átlag, mert egyetlen három héttel később megnyitott üzenet olyan helyre húzza az átlagot, ahol egyetlen üzenet sincs.

Az egyedi találatok

GET /tracking/tmsg_…/opens?includeMachine=true
{    "object": "list",    "data": [      {        "object": "open",        "id": "opn_1a7c…",        "trackedMessageId": "tmsg_9c1f7b2e4a5d40b8a3e61d2f",        "recipient": "[email protected]",        "kind": "machine",        "counted": false,        "client": "Apple Mail Privacy Protection",        "device": "unknown",        "os": "macOS",        "country": "GB",        "region": "England",        "city": "London",        "createdAt": "2026-08-29T08:19:11.000Z"      }    ]  }

A kind értéke human, proxy vagy machine, a counted pedig megmondja, mozdította-e a számokat. A gépi találatokat kizárjuk, hacsak nem adod át az includeMachine=true paramétert, és ez a becsületes alapértelmezés: azért rögzítjük őket, mert az elhagyásuk megmagyarázhatatlan rést hagyna, nem mert érdeklődést jelentenének.

A helymeghatározás durva, mert csak ennyi van. Egyetlen találathoz sem tárolunk IP-címet. Az ország, a régió és a város az, amit az edge amúgy is tudott, és az egyetlen más megőrzött azonosító egy hash, amelynek sója naponta fordul, így egy napon belül meg tudja különböztetni két lekérést, másnap pedig hatástalan.

Amit a számok nem mondhatnak meg

  • Az Apple Mail Privacy Protection kézbesítéskor minden üzenet minden képét lekéri, akár megnézi valaki, akár nem. A User-Agent és a hálózat alapján osztályozzuk, és machine értékként rögzítjük, ahogy mindent, ami a küldéstől számított tíz másodpercen belül érkezik, mert amit ember csinál, az nem történik ilyen gyorsan.
  • A Gmail képproxyja proxy, nem machine: valaki megjelenítette az üzenetet, tehát a megnyitás valódi, miközben az eszköz, a kliens és a hely nem megismerhető. A proxy ráadásul gyorsítótáraz, így egy második olvasás lehet, hogy el sem jut hozzánk. A Gmailen át mért számok alsó korlátok, soha nem összegek.
  • Ugyanannak a példánynak harminc másodpercen belüli két lekérése egy olvasás. Az újrarajzoló előnézeti ablak vagy a visszagördített üzenet újra lekéri a képet; az egy órával későbbi valódi második látogatást viszont továbbra is számoljuk.
  • A címzett megnevezéséhez olyan üzenet kell, amely elég kicsi ahhoz, hogy személyenként újraépítsük: a becsült méret szorozva a címzettek számával 8MB alatt kell maradjon. Efölött egy törzs megy mindenkinek, és minden rajta lévő találat hozzárendelés nélküli.
  • Az az üzenet, amelyen kattintás van, megnyitás nincs, biztosan olvasott: a képeket sokkal gyakrabban blokkolják, mint ahányszor egy linkre nem kattintanak. A két számlálót külön olvasd, ne add össze őket.
  • Ha link nélküli törzsre kérsz kattintáskövetést, semmit nem rögzítünk: a kimenő bájtok azonosak egy nem követett küldéssel, és az ezzel ellentétes sort semmivel nem lehetne összeegyeztetni. Ugyanez igaz arra az üzenetre, amelynek nincs átírható törzse.
  • Az OpenEmail kiszedi az 1×1-es képeket abból a levélből, amelyet a saját felhasználói olvasnak, az általa küldött pixellel együtt, és maga rögzíti a megnyitást, amikor az üzenetet megjelenített képekkel nyitják meg. Az a találat human, az OpenEmail klienssel. Elrejtett képekkel semmit nem rögzítünk.

A GET /tracking/{id} és a GET /emails/{id}/tracking 404 választ ad arra az üzenetre, amelyet soha nem követtünk, nem üres jelentést. A „semmit nem rögzítettünk” és a „senki nem nyitotta meg” két különböző válasz, és nem oszthatnak közös választ. A listavégponton csak követett üzenetek vannak, így a nem követett egyszerűen hiányzik belőle, nem nullákkal szerepel.

Értesülni ahelyett, hogy kérdeznél

A számolt megnyitás email.opened, a számolt kattintás email.clicked eseményt vált ki minden feliratkozott végponton, és mindkettő bekerül az üzenet saját eseménynyomvonalába, ha az ezen az API-n ment át. Egyik sem sül el szkennernél vagy adatvédelmi proxynál. Azok kitolása pontosan azzal a forgalommal töltené meg a fogadó naplóját, amelyet az osztályozó a számokon kívül igyekszik tartani.

A letöltési linkként kiment fájl ugyanígy jelent. A számolt letöltés email.downloaded eseményt vált ki, ugyanarra a nyomvonalra kerül, és ugyanaz az osztályozó tartja ki belőle a szkennereket és a linkelőnézőket, így a szám embereket jelent. A hasznos teher megnevezi a fájlt (shareId, fileId, filename, mimeType, sizeBytes, url) a downloadCount, a first és a downloadedAt mezőkkel, azok mellett a kliens- és helymezők mellett, amelyeket egy kattintás is visz. A recipient mindig null, az attributed mindig hamis: a letöltési link egyetlen URL az üzenet minden címzettjének, így egy letöltést nem lehet egyikükhöz kötni.

Az SDK-ból

openemail.tracking
const report = await openemail.tracking.get('msg_c5f21cc6bfec…')const cold = await openemail.tracking.list({ days: 30, opened: false })const stats = await openemail.tracking.getStats({  days: 30,  offsetMinutes: -new Date().getTimezoneOffset(),})

Itt minden hívás egyszerű olvasás, és a kliens mindegyiket külön próbálja újra. A get OpenEmailApiError hibát dob, amelynek isNotFound mezője igaz arra az üzenetre, amelyet soha nem követtünk, és ez az a megkülönböztetés, amelyet érdemes megőrizni abban, amibe betáplálod.