Tracking
How PostStack records opens and clicks, how to turn it off per domain or per email, and which hits it discounts as machines rather than people.
What gets tracked#
Tracking is applied to the HTML part of each email when it is queued. The plain-text part is never changed, so a text-only email is never tracked. Two settings on the sending domain — the domain of your from address — decide what happens, and both are on for a new domain:
| Setting | What PostStack does to the HTML | Event recorded |
|---|---|---|
open_tracking | Adds a hidden 1×1 image just before the first </body> (or at the end if there is none). | email.opened |
click_tracking | Rewrites every <a href="http…"> and <a href="https…"> link to pass through a redirect that records the click and sends the reader on with a 302. | email.clicked |
Click tracking leaves these links exactly as they are:
mailto:,tel:,#anchorand relative links;- links whose
hrefis in single quotes or unquoted — onlyhref="…"is rewritten; {{unsubscribe_url}}and{{preferences_url}}in emails sent through POST /emails (with or without a template): the tag is still a tag when tracking runs and becomes a link afterwards. In broadcasts those links are filled in first, so they are wrapped and a click on them counts as a click.
Tracked links and the pixel point at https://poststack.dev/api/tracking/…. The original destination is inside a signed token in the link, so it cannot be altered to redirect somewhere else.
Turning tracking on and off#
Per domain
Set open_tracking and click_tracking when you create a domain, or change them later on PATCH /domains/:id. The change applies to emails queued afterwards.
// The SDK takes the numeric domain id.
await poststack.domains.update(12, {
open_tracking: false,
click_tracking: true,
});# The REST API takes the numeric id or the dom_… id.
curl -X PATCH https://api.poststack.dev/domains/12 \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "open_tracking": false, "click_tracking": true }'Per email
POST /emails takes a tracking object that overrides the domain for that one send. Leave a key out to keep the domain's setting.
tracking.opensbooleanfalseskips the pixel;trueadds it even if the domain has open tracking off.tracking.clicksboolean- Same for link rewriting.
// A password reset: no pixel, and the reset link goes straight to your app.
await poststack.emails.send({
from: 'Acme <security@yourdomain.com>',
to: ['ada@example.com'],
subject: 'Reset your password',
html: '<p><a href="https://app.example.com/reset?token=...">Reset your password</a></p>',
tracking: { opens: false, clicks: false },
});Broadcasts and workflow emails have no per-send override; they follow the sending domain's settings.
Machine opens and clicks#
Many opens and clicks are not people. Mail providers fetch images through proxies, and security gateways open messages and follow every link to scan them — often before the recipient has seen anything. PostStack records every hit, but marks it as a machine fetch when any of these is true:
- the User-Agent is Apple Mail Privacy Protection, which loads images for every message on delivery, opened or not;
- the request comes from a known email-security scanner's IP range;
- it arrives within 15 seconds of PostStack handing the message to the recipient's mail server — faster than a person can open or click.
Machine hits stay in the email's event history but are left out of broadcast open/click counters, A/B test winners and open-rate figures.
Gmail's image proxy is not on that list. Gmail fetches images through it when the recipient opens the message, so a proxy hit is a real open and counts — unless it also matches a rule above (a scanner IP, or within 15 seconds of hand-off). Its client shows as Gmail Image Proxy, since the proxy hides the recipient's own device and location.
Treat opens as a rough signal in any case: clients that block images never fire the pixel, and Apple's proxy traffic hides whether a person looked at all. Clicks are the more reliable measure of engagement.
Client, device and location#
For each open and click PostStack parses the User-Agent into an email client, operating system and device type, and looks the IP address up for an approximate country, region and city. These power the dashboard's provider, device and geography breakdowns.
The location lookup runs on PostStack's own servers against the DB-IP Lite database; recipient IP addresses are not sent to a third-party geolocation service. Private and internal addresses are not looked up. IP geolocation by DB-IP (CC BY 4.0).
Webhook events#
Subscribe to these on a webhook. Each arrives in the usual envelope, { "type", "created_at", "data" }.
| Event | Fires when | data |
|---|---|---|
email.opened | The tracking pixel is loaded (every time). | email_id, ip_address, user_agent |
email.clicked | A tracked link is followed (every time). | email_id, url (the original destination), ip_address, user_agent |
email.unsubscribed | A recipient confirms an unsubscribe link or uses one-click unsubscribe in their mail client. | public_id (the con_… id), id and email, or just email when the address is not a contact |
{
"type": "email.clicked",
"created_at": "2026-03-23T10:02:40.000Z",
"data": {
"email_id": "em_3fz8k1q6w4r9t2y7u0i5o8pl",
"url": "https://app.example.com/dashboard",
"ip_address": "203.0.113.24",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15"
}
}ip_address and user_agent are null when the request carried none.
Custom tracking domains#
A domain has a tracking_domain setting for branded links (e.g. track.yourdomain.com, a CNAME to track.poststack.dev).
Troubleshooting#
Opens show in webhooks but not in the dashboard or broadcast stats
They were machine fetches (Apple Mail Privacy Protection, a scanner, or within 15 seconds of delivery). Webhooks report them; counters don't.
A link in my email isn't tracked
Check that its href is double-quoted and absolute (https://…), that the domain has click_tracking on, and that the send didn't pass tracking: { clicks: false }.
A tracked link opens the PostStack home page
The link was altered after sending (a truncated or re-encoded URL), so its signature no longer verifies and PostStack cannot know the destination. It redirects to the home page rather than to an unverified URL.