Website Tracking
Track page views and custom events on your website with a lightweight JavaScript snippet. Tie anonymous visitors to known contacts.
PushMail's tracking snippet is a lightweight JavaScript tag (~1.5 KB) that you embed on your website. It automatically tracks page views, supports custom events, and links anonymous visitors to known contacts when they identify themselves (e.g. via a signup form).
Quick setup
Get your site ID
Every site in PushMail has a numeric ID. Find it in the dashboard under Sites, or via the API:
curl https://pushmail.dev/api/v1/sites \
-H "Authorization: Bearer pm_live_YOUR_KEY"Note the id field in the response.
Embed the tracking snippet
Add this script tag to every page you want to track, just before the closing </body> tag:
<script src="https://pushmail.dev/api/v1/tracking/script?siteId=YOUR_SITE_ID"></script>Replace YOUR_SITE_ID with your actual site ID (e.g. 1, 42).
That's it. Page views are now being tracked automatically, including SPA navigation via the History API.
Identify visitors (optional)
When a visitor logs in or submits a form, call pushmail.identify() to link their anonymous session to a contact:
<script>
pushmail.identify('user@example.com', {
firstName: 'Jane',
plan: 'pro'
});
</script>This creates or updates the contact and re-attributes all past anonymous page views to them.
JavaScript API
The snippet exposes a global pushmail object with three methods:
pushmail.track(event, properties?)
Track a custom event.
// Track a custom event
pushmail.track('viewed_pricing');
// With properties
pushmail.track('added_to_cart', {
product: 'Enterprise Plan',
value: 299
});Page views are tracked automatically on load and on SPA navigation. You can also call pushmail.page() manually.
pushmail.identify(email, properties?)
Link the current anonymous visitor to a contact by email. If the contact does not exist, it is created automatically with source: "tracking_snippet".
pushmail.identify('jane@example.com', {
firstName: 'Jane',
lastName: 'Doe',
plan: 'starter',
company: 'Acme Inc'
});Properties are merged into the contact's customFields. The special keys firstName and lastName are mapped to the corresponding contact fields when creating a new contact.
pushmail.page(properties?)
Explicitly track a page view. This is called automatically on page load and SPA navigation, but you can call it manually if needed.
pushmail.page({ section: 'docs' });REST API endpoints
The tracking snippet communicates with two public endpoints. You can also call these directly from your backend if you prefer server-side tracking.
POST /api/v1/tracking/event
Record a tracking event.
curl -X POST https://pushmail.dev/api/v1/tracking/event \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"anonymousId": "pm_abc123",
"event": "page_view",
"url": "https://example.com/pricing",
"referrer": "https://google.com",
"properties": { "campaign": "spring-sale" }
}'| Field | Type | Required | Description |
|---|---|---|---|
siteId | number | Yes | Your site ID |
event | string | Yes | Event name (page_view, custom names) |
anonymousId | string | No* | Anonymous visitor ID (from localStorage) |
email | string | No* | Contact email (alternative to anonymousId) |
url | string | No | Page URL |
referrer | string | No | Referrer URL |
properties | object | No | Custom event properties (stored as JSON) |
* At least one of anonymousId or email is required.
POST /api/v1/tracking/identify
Link an anonymous visitor to a known contact.
curl -X POST https://pushmail.dev/api/v1/tracking/identify \
-H "Content-Type: application/json" \
-d '{
"siteId": 1,
"email": "jane@example.com",
"anonymousId": "pm_abc123",
"properties": {
"firstName": "Jane",
"plan": "pro"
}
}'| Field | Type | Required | Description |
|---|---|---|---|
siteId | number | Yes | Your site ID |
email | string | Yes | Contact email address |
anonymousId | string | No | Anonymous ID to re-attribute past events |
properties | object | No | Custom fields to set on the contact |
GET /api/v1/tracking/script
Returns the JavaScript tracking snippet. Not typically called directly -- use the <script> tag instead.
| Param | Type | Required | Description |
|---|---|---|---|
siteId | number | Yes | Your site ID (query parameter) |
How it works
- When the script loads, it generates a random anonymous ID and stores it in
localStorageunder the keypm_aid. - On every page load (and SPA navigation), it sends a
page_viewevent to/api/v1/tracking/eventwith the anonymous ID, URL, and referrer. - When you call
pushmail.identify(email), it sends the email and anonymous ID to/api/v1/tracking/identify. PushMail looks up or creates the contact, then re-attributes all past anonymous page views to them. - After identification, subsequent
track()calls still use the anonymous ID, but the server can join them to the contact via thepage_viewstable.
Rate limits
The tracking endpoints are rate limited per site to prevent abuse:
| Endpoint | Limit |
|---|---|
/tracking/event | 100 requests/minute per site |
/tracking/identify | 30 requests/minute per site |
These are public endpoints and do not require an API key. They are validated by siteId.
Security
- The tracking endpoints are intentionally public (no API key needed) so the snippet can run on any website.
- The
siteIdis visible in the script tag URL. This is safe -- it only allows writing tracking events, not reading data. - Rate limiting prevents abuse.
- No cookies are used. The anonymous ID is stored in
localStorageonly.
Framework integration examples
Next.js (App Router)
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<Script
src="https://pushmail.dev/api/v1/tracking/script?siteId=1"
strategy="afterInteractive"
/>
</body>
</html>
);
}React SPA
import { useEffect } from 'react';
function App() {
useEffect(() => {
const script = document.createElement('script');
script.src = 'https://pushmail.dev/api/v1/tracking/script?siteId=1';
script.async = true;
document.body.appendChild(script);
}, []);
// After user logs in:
function onLogin(user: { email: string; name: string }) {
window.pushmail?.identify(user.email, { firstName: user.name });
}
return <div>...</div>;
}Server-side (Node.js)
If you prefer server-side tracking, call the endpoints directly:
async function trackEvent(siteId: number, email: string, event: string) {
await fetch('https://pushmail.dev/api/v1/tracking/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ siteId, email, event }),
});
}
// Track a purchase
await trackEvent(1, 'jane@example.com', 'purchase');Email Providers
PushMail supports 10 email providers via BYOK (Bring Your Own Key). Connect your own SendGrid, SES, Postmark, Mailgun, or any supported provider to send through your own infrastructure.
MCP Server
Connect AI agents like Claude, GPT, and Cursor to manage your PushMail email infrastructure through natural language.