senderwolf-open-tracker
v1.0.0
Published
Injects a transparent 1x1 tracking pixel and rewrites links in HTML emails, with a built-in HTTP server for open-rate and click analytics
Maintainers
Readme
@senderwolf/plugin-open-tracker
Automatically injects a transparent 1×1 tracking pixel into the HTML body of outgoing emails and rewrites links to route through a tiny built-in HTTP server, providing open-rate and click analytics for free.
Zero external dependencies. Uses only Node.js built-ins (node:http, node:crypto).
Installation
npm install @senderwolf/plugin-open-trackerQuick Start
import { sendEmail } from 'senderwolf';
import { OpenTracker } from '@senderwolf/plugin-open-tracker';
const tracker = new OpenTracker({
baseUrl: 'http://localhost:3001', // must be reachable by email clients
onOpen: (info) => console.log('📬 Opened by:', info.to, 'at', new Date(info.timestamp).toISOString()),
onClick: (info) => console.log('🔗 Clicked:', info.originalUrl, 'by', info.to),
});
// Start the tracking HTTP server
await tracker.startServer();
// Instrument your mail options (injects pixel + rewrites links)
const { mailOptions, emailId } = tracker.instrument({
to: '[email protected]',
subject: 'Hello from Senderwolf!',
html: `
<h1>Hello!</h1>
<p>Check out our <a href="https://example.com/offers">latest offers</a>.</p>
<p>Or <a href="https://example.com/unsubscribe">unsubscribe</a>.</p>
`,
});
console.log('Tracking email as:', emailId);
await sendEmail({
smtp: { auth: { user: '...', pass: '...' } },
mail: mailOptions,
});
// Later — check stats
const stats = tracker.getStats();
console.log(`Opens: ${stats.totalOpens}, Clicks: ${stats.totalClicks}`);
// Shutdown
await tracker.stopServer();HTTP Endpoints
| Endpoint | Description |
|---|---|
| GET /track/open/:emailId?to=&subject= | Serves a 1×1 transparent GIF, records an open event |
| GET /track/click/:emailId/:linkId?url=&to= | Records click, fires onClick, then 302 redirects to original URL |
| GET /analytics | Returns full TrackerStats JSON |
| GET /health | { status: "ok" } |
API
new OpenTracker(options)
| Option | Type | Required | Description |
|---|---|---|---|
| baseUrl | string | ✅ | Public URL of your tracking server |
| port | number | — | HTTP server port (default: 3001) |
| onOpen | Function | — | Called when a pixel is loaded: (info: OpenRecord) => void |
| onClick | Function | — | Called when a link is clicked: (info: ClickRecord) => void |
.instrument(mailOptions) → { mailOptions, emailId }
Mutates the html field of the provided mail options:
- Rewrites
<a href="...">links (skipsmailto:,tel:,#anchors) - Appends
<img>tracking pixel before</body>(or at end of HTML)
Returns the modified mailOptions and the assigned emailId. Spread mailOptions into sendEmail.
.startServer(port?)
Starts the built-in tracking server. Returns Promise<void>.
.stopServer()
Gracefully closes the server. Returns Promise<void>.
.getStats() → TrackerStats
{
opens: OpenRecord[]; // all recorded open events
clicks: ClickRecord[]; // all recorded click events
totalOpens: number;
totalClicks: number;
}Notes
- Production: Point
baseUrlto a publicly accessible server (e.g. a VPS, Railway, Fly.io app). The tracking pixel only fires when an email client loads images. - Privacy: This plugin stores data in memory by default. For persistence, integrate
getStats()with your own DB in theonOpen/onClickcallbacks. - Link rewriting skips
mailto:,tel:, and#anchor links automatically.
License
MIT © Chandraprakash
