Open और click tracking
GET /tracking: संदेश पढ़ा गया या नहीं, और किस पर क्लिक हुआ।
इस पेज की 6 कॉल में से कोई भी आपकी अपनी कुंजी से आपके वर्कस्पेस पर चलाता है।
क्या दर्ज होता है
दो स्वतंत्र स्विच, दोनों तब तक चालू जब तक उन्हें उस पते के लिए या All addresses के लिए बंद न किया गया हो जिससे संदेश भेजा जाता है। opens एक 1×1 छवि जोड़ता है; clicks बॉडी के नए हिस्से के लिंक दोबारा लिखता है। उत्तर के नीचे उद्धृत इतिहास किसी और का संदेश है और उसे छुआ नहीं जाता। भेजाव एक संदेश के लिए निर्णय लेने हेतु tracking: { opens, clicks } देता है (किसी भी दिशा में, इसलिए false वह तरीका है जिससे कोई प्रोग्राम मना करता है कि पता क्या करने को सेट है), और जो फ़ील्ड आप छोड़ देते हैं वह उस पते की सेटिंग पर लौटता है जिससे यह भेजा गया, फिर All addresses पर — न कि किसी ऐसे डिफ़ॉल्ट पर जो यह API वर्कस्पेस की ओर से चुन ले।
{ "from": "Acme Billing <[email protected]>", "to": ["[email protected]"], "subject": "Your September invoice", "html": "<p>Invoice attached.</p>", "tracking": { "opens": true, "clicks": true } }प्रति संदेश अधिकतम 100 गंतव्य दोबारा लिखे जाते हैं, हर एक एक बार। किसी header छवि, किसी बटन और किसी footer से जुड़ा एक ही URL एक ही पंक्ति है, क्योंकि वह एक ही सवाल तीन बार पूछा जाना है। सीमा के बाद बचे हुए लिंक ठीक वैसे ही छोड़ दिए जाते हैं जैसे लिखे गए थे: बिना track वाला लिंक भी काम करता है, और जो संदेश चुपचाप अपने आख़िरी दो सौ लिंक गँवा दे वह अधूरी रिपोर्ट से कहीं बड़ी विफलता है।
दोबारा लिखे गए लिंक और पिक्सेल डिफ़ॉल्ट रूप से OpenEmail API होस्ट की ओर इंगित करते हैं। जब भेजने वाले डोमेन के पास कोई कस्टम tracking डोमेन हो जिसका tracking.status active है, तो उस डोमेन से जाने वाला नया मेल इसके बजाय https://<tracking host>/t/... का उपयोग करता है, और PATCH /domains/{id} वह जगह है जहाँ आप इसे सेट करते हैं।
इस सबके लिए emails:read चाहिए, और कोई tracking scope नहीं है। उस scope का अर्थ पहले से ही है "भेजे गए संदेश और उनकी डिलीवरी स्थिति पढ़ें", और किसी ने संदेश खोला या नहीं, यह सबसे शाब्दिक डिलीवरी स्थिति है।
एंडपॉइंट
| कॉल | क्या लौटाता है |
|---|---|
| `GET /tracking` | track किए गए संदेश, नए से पुराने। opened, clicked, days (1–365, डिफ़ॉल्ट 30), limit (अधिकतम 200)। |
| `GET /tracking/stats` | किसी अवधि पर दरें। days (डिफ़ॉल्ट 30) और offsetMinutes, ताकि दिन वहीं टूटें जहाँ पढ़ने वाले का दिन टूटता है। |
| `GET /tracking/{id}` | एक रिपोर्ट। यह tmsg_ वाली tracking id लेता है या वह msg_ id जो भेजाव ने लौटाई थी। |
| `GET /tracking/{id}/opens` | अलग-अलग fetch। includeMachine, limit (अधिकतम 200)। |
| `GET /tracking/{id}/clicks` | वही, हर पंक्ति पर linkId और url के साथ। |
| `GET /emails/{id}/tracking` | वही रिपोर्ट, उस send id से जो आपके पास पहले से है। |
क्वेरी स्ट्रिंग में boolean पूरे लिखे जाते हैं: true, false, 1 या 0, और बाकी कुछ भी अस्वीकार है। Boolean("false") true होता है, इसलिए ज़बरदस्ती बदला गया ?opened=false ठीक उल्टा लौटा देता जो माँगा गया था।
यह /emails पर कुछ फ़ील्ड होने के बजाय अपना अलग संसाधन इसलिए है क्योंकि कवरेज का सवाल है: वह सूची send रिकॉर्ड रखती है, और composer, MCP टूल तथा असिस्टेंट सभी बिना कोई रिकॉर्ड लिखे भेजते हैं। उस पर बनी रिपोर्ट मेलबॉक्स के बारे में नहीं, आपके API ट्रैफ़िक के बारे में रिपोर्ट होती।
रिपोर्ट
{ "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 } ] }opens और clicks वह हैं जो संदेश पर लागू किए गए थे; opened और clicked वह हैं जो हुआ। openCount पढ़े जाने गिनता है और openCountRaw fetch गिनता है। यहाँ चार का जो अंतर है वह स्कैनर और प्राइवेसी प्रॉक्सी हैं, जिन्हें इसलिए रखा जाता है ताकि लॉग और कुल के बीच का फ़ासला अस्पष्ट रहने के बजाय जाँचा जा सके। किसी का नाम लेने से पहले attributable वही फ़ील्ड है जिसे पढ़ना चाहिए: false का अर्थ है कि पढ़ा जाना उस प्रति पर हुआ जो पूरी सूची को गई थी, और उसके बाद किसी ख़ास प्राप्तकर्ता के बारे में कहा गया हर वाक्य एक अनुमान है।
source उस सतह का नाम लेता है जिसने इसे भेजा: इस API से भेजे गए के लिए api, और ऐप ने जो कुछ भी भेजा उसके लिए composer। दूसरी तरह वालों के लिए sendId null होता है, और इसीलिए tracking id मौजूद है।
null email और attributed: false वाली पंक्ति वह जगह है जहाँ ऐसा पढ़ा जाना दर्ज होता है जिसे किसी व्यक्ति से जोड़ा नहीं जा सका, और रिपोर्ट ऐसी पंक्ति तभी दिखाती है जब वाकई कोई पढ़ना हुआ हो। एक ही प्राप्तकर्ता वाले संदेश में ऐसी कोई पंक्ति होती ही नहीं, क्योंकि एक बॉडी और एक पता एक ही कथन हैं। कई प्राप्तकर्ताओं वाले संदेश में वह जाने के क्षण से ही पीछे मौजूद रहती है, क्योंकि dispatch तक ट्रांसपोर्ट तय नहीं होता, और जब तक उस पर कुछ आता नहीं वह रिपोर्ट से बाहर रहती है: नामित प्राप्तकर्ताओं के बगल में स्थायी "someone: not opened" ऐसी पंक्ति है जिसे केवल ग़लत ही पढ़ा जा सकता है। जहाँ वह मौजूद है, वहाँ नामित पंक्तियाँ शून्य पर बैठी होती हैं और attributable false होता है। पढ़ा जाना असली है, पढ़ने वाला संदेश के लोगों में से एक है, और "इस संदेश पर कोई" ही एकमात्र प्रस्तुति है जिसे यह डेटा सहारा देता है। प्राप्तकर्ता सूची से नाम कभी न भरें।
किसी अवधि पर दरें
{ "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 }] }दरें track किए गए संदेशों पर प्रतिशत हैं, भेजे गए पूरे मेल पर नहीं: जो वर्कस्पेस दस में से एक संदेश track करता है उसकी open rate उन्हीं दस के लिए है, और सब कुछ जो उसने कभी भेजा उससे भाग देने पर वह हर बार गिरती जब कोई बिना track वाला उत्तर भेजता। पाँच बार खोला गया संदेश एक ही खोला गया संदेश है। दरें संदेश गिनती हैं और कुल योग हिट गिनते हैं, और इन दोनों को मिला देना ही वह तरीका है जिससे 100% से ऊपर की open rates प्रकाशित होती हैं।
byDay विरल है: जिस दिन कुछ भी track नहीं हुआ वह शून्य के बजाय अनुपस्थित रहता है, इसलिए चार्ट बनाने से पहले खाली जगहें भरें। दिन UTC से offsetMinutes पूर्व (−840 से 840) पर बाँटे जाते हैं ताकि वे वहीं टूटें जहाँ पढ़ने वाले का दिन टूटता है। medianTimeToOpenSeconds माध्य नहीं, माध्यिका है, क्योंकि तीन हफ़्ते देर से खोला गया एक संदेश औसत को वहाँ खींच ले जाता है जहाँ वास्तव में कोई संदेश है ही नहीं।
अलग-अलग हिट
{ "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" } ] }kind human, proxy या machine होता है, और counted बताता है कि उसने संख्याएँ हिलाईं या नहीं। मशीन हिट तब तक बाहर रखी जाती हैं जब तक आप includeMachine=true न भेजें, और यही ईमानदार डिफ़ॉल्ट है: वे इसलिए दर्ज होती हैं कि उन्हें छोड़ देने पर एक अस्पष्ट खाई रह जाती, इसलिए नहीं कि वे जुड़ाव हैं।
स्थान मोटा-मोटी है क्योंकि इतना ही है। किसी भी हिट के लिए कोई IP पता संग्रहीत नहीं होता। देश, क्षेत्र और शहर वही हैं जो edge को पहले से पता था, और रखी जाने वाली एकमात्र दूसरी पहचान एक hash है जिसका salt रोज़ बदलता है, इसलिए वह एक दिन के भीतर दो fetch में फ़र्क बता सकता है और अगले दिन निष्क्रिय हो जाता है।
संख्याएँ क्या नहीं कह सकतीं
- Apple Mail Privacy Protection डिलीवरी के समय हर संदेश की हर छवि ले आता है, चाहे कोई देखे या न देखे। इसे User-Agent और नेटवर्क से वर्गीकृत करके
machineके रूप में दर्ज किया जाता है, और भेजाव के दस सेकंड के भीतर आने वाली हर चीज़ भी, क्योंकि कोई व्यक्ति इतनी तेज़ी से कुछ नहीं करता। - Gmail का इमेज प्रॉक्सी
machineनहीं,proxyहै: किसी ने संदेश प्रदर्शित किया, इसलिए open असली है, जबकि डिवाइस, क्लाइंट और स्थान जाने नहीं जा सकते। प्रॉक्सी कैश भी करता है, इसलिए दूसरी बार पढ़ा जाना हम तक कभी पहुँचे ही नहीं। Gmail से आने वाली गिनती एक न्यूनतम सीमा है, कभी कुल नहीं। - एक ही प्रति के तीस सेकंड के भीतर दो fetch एक ही पढ़ना हैं। preview pane का दोबारा बनना या संदेश का फिर से दृश्य में आना छवि दोबारा ले आता है; एक घंटे बाद की असली दूसरी यात्रा फिर भी गिनी जाती है।
- प्राप्तकर्ता का नाम लेने के लिए संदेश इतना छोटा होना चाहिए कि हर व्यक्ति के लिए दोबारा बनाया जा सके: अनुमानित आकार गुणा प्राप्तकर्ताओं की संख्या 8MB से कम रहनी चाहिए। उससे ऊपर एक ही बॉडी सबके पास जाती है, और उस पर हर हिट बिना श्रेय के रहती है।
- जिस संदेश पर clicks हैं और opens नहीं, वह निश्चित रूप से पढ़ा गया है: लिंक पर क्लिक न होने की तुलना में छवियाँ कहीं ज़्यादा बार रोकी जाती हैं। दोनों काउंटर जोड़ने के बजाय अलग-अलग पढ़ें।
- बिना लिंक वाली बॉडी पर clicks माँगने से कुछ भी दर्ज नहीं होता: जो बाइट्स गईं वे बिना track वाले भेजाव जैसी ही हैं, और इसके उलट दावा करने वाली पंक्ति किसी चीज़ से मेल नहीं खा सकती। यही बात उस संदेश पर भी लागू होती है जिसमें दोबारा लिखने के लिए बॉडी ही नहीं है।
- OpenEmail अपने ही उपयोगकर्ताओं द्वारा पढ़े जाने वाले मेल से 1×1 छवियाँ हटा देता है, अपने भेजे पिक्सेल सहित, और जब संदेश छवियों के साथ प्रदर्शित होता है तो open ख़ुद दर्ज करता है। वह हिट
humanहोती है और क्लाइंटOpenEmail। छवियाँ छिपी होने पर कुछ भी दर्ज नहीं होता।
जिस संदेश को कभी track नहीं किया गया, उसके लिए GET /tracking/{id} और GET /emails/{id}/tracking खाली रिपोर्ट के बजाय 404 देते हैं। "हमने कुछ दर्ज नहीं किया" और "किसी ने इसे नहीं खोला" अलग-अलग उत्तर हैं और इन्हें एक ही प्रतिक्रिया साझा नहीं करनी चाहिए। सूची एंडपॉइंट में केवल track किए गए संदेश होते हैं, इसलिए बिना track वाला संदेश शून्यों के साथ मौजूद होने के बजाय उसमें होता ही नहीं।
पूछने के बजाय बताया जाना
गिना गया open हर सदस्यता ले चुके एंडपॉइंट पर email.opened चलाता है और गिना गया click email.clicked, और जहाँ संदेश इस API से गया हो वहाँ दोनों उसके अपने इवेंट सिलसिले में भी लिखे जाते हैं। स्कैनर या प्राइवेसी प्रॉक्सी के लिए इनमें से कोई नहीं चलता। उन्हें भेजने से प्राप्तकर्ता का लॉग ठीक उसी ट्रैफ़िक से भर जाता जिसे संख्याओं से बाहर रखने के लिए वर्गीकारक मौजूद है।
जो फ़ाइल डाउनलोड लिंक के रूप में गई वह भी इसी तरह रिपोर्ट करती है। गिना गया डाउनलोड email.downloaded चलाता है और उसी सिलसिले में आता है, और वही वर्गीकारक स्कैनर और लिंक प्रीव्यू करने वालों को उससे बाहर रखता है, इसलिए गिनती लोगों की होती है। पेलोड फ़ाइल का नाम लेता है (shareId, fileId, filename, mimeType, sizeBytes, url) और उसके साथ downloadCount, first तथा downloadedAt भी, उन्हीं क्लाइंट और स्थान फ़ील्ड के बगल में जो कोई click लेकर आता है। recipient हमेशा null होता है और attributed हमेशा false: डाउनलोड लिंक संदेश के हर प्राप्तकर्ता के लिए एक ही URL है, इसलिए किसी डाउनलोड को उनमें से किसी एक से नहीं जोड़ा जा सकता।
SDK से
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(),})यहाँ हर कॉल एक सादा read है, और क्लाइंट हर एक को अपने आप दोबारा आज़माता है। जिस संदेश को कभी track नहीं किया गया उसके लिए get एक OpenEmailApiError फेंकता है जिसका isNotFound true होता है, और यही वह फ़र्क है जिसे आप इसे जहाँ भी डालें वहाँ बचाए रखना सार्थक है।