ردیابی بازشدن و کلیک
GET /tracking: اینکه پیامی خوانده شده یا نه، و چه چیزی دنبال شده.
هر کدام از 6 فراخوانی این صفحه را با کلید خودتان روی فضای کاری شما اجرا میکند.
چه چیزی ثبت میشود
دو کلید مستقل، هر دو روشن مگر آنکه برای نشانیای که پیام از آن فرستاده میشود یا برای All addresses خاموش شده باشند. opens یک تصویر ۱×۱ میافزاید؛ 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 } }دستبالا ۱۰۰ مقصد در هر پیام بازنویسی میشود، هرکدام یک بار. همان URL که از یک تصویر سرصفحه، یک دکمه و یک پاورقی پیوند شده یک ردیف است، چون یک پرسش است که سه بار پرسیده شده. پس از سقف، پیوندهای باقیمانده دقیقاً همانطور که نوشته شدهاند رها میشوند: پیوند ردیابینشده هم کار میکند، و پیامی که بیسروصدا دویست پیوند آخرش را از دست بدهد شکستی بهمراتب بدتر از یک گزارش ناقص است.
پیوندهای بازنویسیشده و پیکسل بهطور پیشفرض به میزبان API اوپنایمیل اشاره میکنند. وقتی دامنهٔ فرستنده یک دامنهٔ ردیابی سفارشی داشته باشد که tracking.status آن active است، نامههای تازه از آن دامنه بهجایش از https://<tracking host>/t/... استفاده میکنند، و PATCH /domains/{id} جایی است که یکی را تنظیم میکنید.
همهٔ اینها به emails:read نیاز دارند، و دامنه دسترسیای برای ردیابی وجود ندارد. آن دامنه دسترسی از پیش یعنی «پیامهای فرستادهشده و وضعیت تحویلشان را بخوان»، و اینکه کسی پیامی را باز کرده یا نه، لفظیترین وضعیت تحویل ممکن است.
نقطههای پایانی
| فراخوانی | چه برمیگرداند |
|---|---|
| `GET /tracking` | پیامهای ردیابیشده، تازهترین اول. opened، clicked، days (۱ تا ۳۶۵، پیشفرض ۳۰)، limit (بیشینه ۲۰۰). |
| `GET /tracking/stats` | نرخها روی یک بازه. days (پیشفرض ۳۰) و offsetMinutes، تا روزها همانجا بشکنند که روزِ خواننده میشکند. |
| `GET /tracking/{id}` | یک گزارش. یک شناسهٔ ردیابی tmsg_ یا همان شناسهٔ msg_ را که یک ارسال برگردانده میگیرد. |
| `GET /tracking/{id}/opens` | تکتک واکشیها. includeMachine، limit (بیشینه ۲۰۰). |
| `GET /tracking/{id}/clicks` | همان، با linkId و url روی هر ردیف. |
| `GET /emails/{id}/tracking` | همان گزارش، از روی شناسهٔ ارسالی که از پیش در دست دارید. |
بولیها در رشتهٔ پرسوجو کامل نوشته میشوند: true، false، 1 یا 0، و هر چیز دیگری رد میشود. Boolean("false") درست است، پس یک ?opened=false که تبدیل نوع شده باشد دقیقاً عکس آنچه خواسته شده را برمیگرداند.
این بهجای چند فیلد روی /emails یک منبع مستقل است، به دلیل پوشش: آن فهرست رکوردهای ارسال را نگه میدارد، و نویسنده، ابزارهای 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 واکشیها را. تفاوت، که اینجا چهار است، همان اسکنرها و پراکسیهای حریم خصوصی است که نگه داشته میشوند تا شکاف میان گزارش و مجموع قابل وارسی باشد نه توضیحناپذیر. attributable فیلدی است که باید پیش از نامبردن از کسی خوانده شود: نادرست یعنی یک خواندهشدن روی نسخهای نشسته که به کل فهرست رفته، و هر جملهای پس از آن دربارهٔ یک گیرندهٔ خاص حدس است.
source نام سطحی را میگوید که آن را فرستاده: api برای ارسالی از راه این API، و composer برای هر چیزی که خود برنامه فرستاده. sendId برای نوع دوم null است، و شناسهٔ ردیابی به همین دلیل وجود دارد.
ردیفی با email برابر null و attributed: false همانجاست که خواندهشدنی مینشیند که نتوانسته به کسی سنجاق شود، و یک گزارش تنها وقتی یکی از آنها را نشان میدهد که واقعاً خواندهشدنی رخ داده باشد. پیامی با یک گیرندهٔ یگانه اصلاً چنین ردیفی ندارد، چون یک بدنه و یک مخاطب همان یک گزارهاند. پیامی با چند گیرنده از همان لحظهٔ رفتن یکی پشتش دارد، چون لایهٔ انتقال تا لحظهٔ ارسال قطعی نیست، و تا وقتی چیزی روی آن نرسد از گزارش بیرون میماند: یک «کسی: باز نشد» دائمی کنار گیرندههای نامبرده ردیفی است که جز بدفهمیدهشدن سرنوشتی ندارد. آنجا که این ردیف هست، ردیفهای نامبرده همانهاییاند که روی صفر نشستهاند و 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 }] }نرخها درصدهایی روی پیامهای ردیابیشدهاند، نه روی همهٔ نامههای فرستادهشده: فضای کاریای که یک پیام از هر ده پیام را ردیابی میکند نرخ بازشدنی برای همان ده تا دارد، و تقسیمکردن بر هرآنچه تا به حال فرستاده هر بار که کسی پاسخی ردیابینشده میفرستد پایین میآمد. پیامی که پنج بار باز شده یک پیامِ بازشده است. نرخها پیامها را میشمارند و مجموعها بازدیدها را، و درآمیختن این دو همان راهی است که نرخهای بازشدن بالای ۱۰۰٪ منتشر میشوند.
byDay تُنُک است: روزی که چیزی در آن ردیابی نشده بهجای صفر، غایب است، پس پیش از رسم نمودار شکافها را پر کنید. روزها در offsetMinutes شرقِ UTC (−۸۴۰ تا ۸۴۰) سطلبندی میشوند تا همانجا بشکنند که روزِ خواننده میشکند. 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 ذخیره نمیشود. کشور، منطقه و شهر همان چیزی است که لبه از پیش میدانست، و تنها شناسهٔ دیگری که نگه داشته میشود هشی است که نمکش روزانه میچرخد، پس میتواند دو واکشی را درون یک روز از هم جدا کند و روز بعد بیاثر است.
آنچه اعداد نمیتوانند بگویند
- Apple Mail Privacy Protection هنگام تحویل هر تصویر در هر پیامی را واکشی میکند، چه کسی نگاه کند چه نکند. از روی User-Agent و شبکه طبقهبندی میشود و بهعنوان
machineثبت میگردد، و هر چیزی که در ده ثانیهٔ نخست پس از ارسال برسد هم همینطور، چون هیچ کاری که آدم بکند آنقدر سریع رخ نمیدهد. - پراکسی تصویر Gmail بهجای
machineدر دستهٔproxyاست: کسی پیام را نمایش داده، پس بازشدن واقعی است، در حالی که دستگاه، کلاینت و مکان دانستنی نیستند. این پراکسی کش هم میکند، پس خواندهشدن دوم شاید اصلاً به ما نرسد. شمارشها از راه Gmail یک کفاند، هرگز یک مجموع. - دو واکشی از یک نسخه در فاصلهٔ سی ثانیه یک خواندهشدن است. بازترسیم یک پنجرهٔ پیشنمایش یا پیامی که دوباره به دید اسکرول شود تصویر را دوباره واکشی میکند؛ بازدید دوم واقعی یک ساعت بعد همچنان شمرده میشود.
- نامبردن از گیرنده به پیامی نیاز دارد که بهاندازهٔ کافی کوچک باشد تا برای هر نفر از نو ساخته شود: اندازهٔ برآوردی ضرب در شمار گیرندهها باید زیر ۸ مگابایت بیاید. بالاتر از آن، یک بدنه به همه میرود و هر بازدیدی روی آن بیانتساب است.
- پیامی با کلیک و بدون بازشدن قطعاً خوانده شده است: تصاویر بهمراتب بیشتر از آنکه پیوندها بیکلیک بمانند مسدود میشوند. این دو شمارنده را بهجای جمعبستن، جدا بخوانید.
- درخواست کلیک روی بدنهای بدون پیوند اصلاً چیزی ثبت نمیکند: بایتهایی که بیرون رفتهاند با یک ارسال ردیابینشده یکساناند، و ردیفی که خلافش را ادعا کند با هیچ چیزی جور درنمیآید. دربارهٔ پیامی که بدنهای برای بازنویسی ندارد هم همین درست است.
- اوپنایمیل تصاویر ۱×۱ را از نامههایی که کاربران خودش میخوانند بیرون میکشد، از جمله پیکسلی که خودش میفرستد، و وقتی پیامی با تصاویرِ نمایان نشان داده شود خودش بازشدن را ثبت میکند. آن بازدید
humanاست با کلاینتِOpenEmail. با تصاویرِ پنهان چیزی ثبت نمیشود.
GET /tracking/{id} و GET /emails/{id}/tracking برای پیامی که هرگز ردیابی نشده 404 پاسخ میدهند، نه یک گزارش خالی. عبارتهای «ما چیزی ثبت نکردیم» و «کسی بازش نکرد» دو پاسخ متفاوتاند و نباید یک پاسخ مشترک داشته باشند. نقطه پایانی فهرست تنها پیامهای ردیابیشده را نگه میدارد، پس پیام ردیابینشده بهسادگی از آن غایب است نه حاضر با صفرها.
به شما گفته شود بهجای آنکه بپرسید
یک بازشدنِ شمردهشده email.opened و یک کلیکِ شمردهشده email.clicked را روی هر نقطه پایانی مشترک شلیک میکند، و هر دو آنجا که از این API گذشتهاند در رد رویدادهای خود پیام هم نوشته میشوند. هیچکدام برای یک اسکنر یا یک پراکسی حریم خصوصی شلیک نمیشوند. هلدادن آنها گزارش گیرنده را دقیقاً با همان ترافیکی پر میکرد که طبقهبند برای بیروننگهداشتنش از اعداد وجود دارد.
فایلی که بهصورت پیوند دانلود بیرون رفته به همین شکل گزارش میشود. یک دانلودِ شمردهشده email.downloaded را شلیک میکند و روی همان رد مینشیند، و همان طبقهبند اسکنرها و پیشنمایشگرهای پیوند را از آن بیرون نگه میدارد، پس شمارش، آدم است. بار داده فایل را نام میبرد (shareId، fileId، filename، mimeType، sizeBytes، url) همراه با downloadCount، first و downloadedAt در کنار فیلدهای کلاینت و مکان که یک کلیک با خود دارد. recipient همیشه null است و attributed همیشه نادرست: پیوند دانلود یک 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(),})هر فراخوانی اینجا یک خواندن ساده است، و کلاینت هرکدام را جداگانه دوباره تلاش میکند. get یک OpenEmailApiError پرتاب میکند که isNotFound آن برای پیامی که هرگز ردیابی نشده درست است، و همین تمایزی است که ارزش دارد در هر چیزی که آن را به آن میخورانید حفظ شود.