Hulo Global
Plugins Roadmap

Email Tracking

Per-link transactional email tracking with human-vs-machine classification, sensitive-link redaction and a per-order activity timeline.

1,818 npm downloads last monthListed in the Vendure plugin directoryVendure 3.5 – 3.7MySQL · MariaDB · PostgreSQLAGPL source + commercial licence

What it does

Built for production from day one.

Drop-in tracker for every email your Vendure server sends. Wraps the `@vendure/email-plugin` pipeline plus a service for ad-hoc sends. Records every send, open and click as a raw event — never deduped — so the underlying audit trail is always intact. Each link in each email gets its own opaque token so the click handler can identify **exactly which link was clicked** (invoice view, order view, password reset, footer terms link) rather than merely "a link was clicked". Password-reset and invoice-access links are flagged sensitive so the raw destination never lands in the event log. Every open + click is classified `human_likely` / `machine_likely` / `unknown` with reason codes (Gmail image proxy, Apple Mail Privacy Protection, Microsoft Safe Links, Proofpoint, Mimecast, Barracuda, datacentre, VPN, bot UA, …) so you can tell a real customer from a security scanner.

Per-link tokenisation

Every clickable link in every email gets a random 32-byte token; the destination is stored server-side. The click endpoint identifies exactly which link was clicked — link_type, link_label, link_text, link_index, template_section, destination host + path — and records it on the event row.

Sensitive-link redaction

Flag password-reset, invoice-access-token and licence-key URLs with `isSensitive: true`. The raw destination is replaced with `[sensitive: <host>]` in the event log; only the URL hash + host are stored. The redirect still works — the raw destination just never lands on an admin-visible row.

Human / machine / unknown classification

Every open and click is scored with the SDK's built-in classifier. Reason chips surface why an event was flagged machine-likely: `gmail-proxy`, `ampp` (Apple Mail Privacy Protection), `safelinks`, `outlook-proxy`, `proofpoint`, `mimecast`, `barracuda`, `symantec`, `datacentre`, `vpn`, `tor`, `bot-ua`, `headless`, `prefetch`, `scanner-ua`. Raw event count still recorded — the classification is advisory, never the sole basis for a decision.

IP enrichment (ip-api.com or ipinfo)

Every event ingested triggers an async geo lookup (country, region, city, ASN, organisation, timezone, proxy / VPN / datacentre / mobile flags). Provider is pluggable via `HULO_IP_ENRICHMENT_PROVIDER`. Skips private / loopback / link-local. Never blocks event recording — enrichment happens fire-and-forget.

Provider webhook receiver

Ingest delivered / bounced / deferred / dropped / complaint / open / click events from Postmark, SendGrid, Mailgun and Amazon SES. Each provider's signature scheme is verified; unconfigured providers 401; unknown slugs 404. Idempotent within 24h so retries don't double-count.

Full open + click history per email

Raw events are never deduplicated. Multiple pixel fires from the same recipient create multiple rows; the admin UI groups for display but the underlying audit trail stays intact.

Suppression list

Hard bounces and complaints auto-add to the suppression table. Subsequent sends are silently skipped and logged as `status=suppressed`.

Per-template analytics

Open rate, CTR, click-to-open ratio and bounce rate per email type — order-confirmation, OTP, invoice, password-reset and your custom types.

Bounce + complaint webhook

POST DSN events to `/email-track/bounce` from your postmaster integration for legacy setups.

Admin UI: Email Log + per-customer Emails tab

Filter by recipient, customer, order, status, type, date range. Expand any row for the full event timeline.

Order Activity History panel

On every order-detail page: chronological timeline of every transactional email event with classification badges, filters (opens only / clicks only / payment / admin notes), IP-derived location with cautionary banner, CSV export, PDF Evidence Report via short-lived signed URLs, and elevated-permission full-export for legal evidence preservation. Pagination for high-volume customers — 100 events per page with a "Load more" button.

CSV + JSON + PDF export

CSV (redacted) for daily browsing, JSON via GraphQL for machine processing, PDF Evidence Report ("Order Activity and Delivery Evidence Report") for legal preservation. Sensitive fields respect the redaction rules; full export requires elevated permission.

Works with any SMTP transport

Gmail, SES, SendGrid, Postmark, Mailgun, raw SMTP. Just plug `TrackingEmailSender()` into your email-plugin config.

Graceful degradation

No runtime import from invoice / support-ticket / order plugins. Hosts that don't have those simply never pass the id; entity foreign-id columns are plain nullable ints. If a table isn't there yet, persistence fails silently and the redirect still works via signature verification.

MySQL, MariaDB & PostgreSQL

The plugin follows whatever database your Vendure `dbConnectionOptions` use — no configuration. Verified against PostgreSQL 17; MySQL/MariaDB installs are unchanged.

Buy & activate from the admin

Start the 14-day free trial, subscribe or buy lifetime from the plugin's admin page — checkout opens in a new tab and the key installs itself within a minute, renewals included. The same card shows your plan, first-charge date and a <strong>Manage billing</strong> link to the Stripe portal (update card, cancel, switch plan). Already have a key? Paste it into the same banner. No `.env` edit, no redeploy; environment keys still take precedence for infrastructure-as-code setups.

One-click in-app updates

When a new version ships, an update banner shows current → latest with a What's-new link to the changelog. "Update now" installs the registry-verified release via your project's own package manager (yarn/npm/pnpm auto-detected) and gracefully restarts under pm2/systemd. Disable with `HULO_SELF_UPDATE=off`.

Install

Four steps, five minutes.

Add the package

Or run the one-line installer that does steps 1–3 for you:

curl -sSL https://huloglobal.com/vendure-plugins/email-tracking/install.sh | bash

Prefer to do it by hand?

yarn add @huloglobal/vendure-plugin-email-tracking # or npm install @huloglobal/vendure-plugin-email-tracking # or pnpm add @huloglobal/vendure-plugin-email-tracking

Register it

In your vendure-config.ts:

# vendure-config.ts import { EmailTrackingPlugin } from '@huloglobal/vendure-plugin-email-tracking'; export const config: VendureConfig = { plugins: [ EmailTrackingPlugin.init({ publicBaseUrl: 'https://shop.example.com', licenceKey: process.env.HULO_LICENCE_KEY_EMAIL_TRACKING, }), // ... your other plugins ], };

Compile the admin UI

Add the extension to your compileUiExtensions call so the plugin's page appears in the admin:

// compile-admin-ui.ts import { compileUiExtensions } from '@vendure/ui-devkit/compiler'; import { EmailTrackingPlugin } from '@huloglobal/vendure-plugin-email-tracking'; compileUiExtensions({ outputPath: path.join(__dirname, 'admin-ui'), extensions: [EmailTrackingPlugin.uiExtensions], });

Run the migration

The plugin registers its own entities. Generate and run the migration like any other:

yarn migration:generate AddEmailTrackingPluginTables yarn migration:run

That's it. Restart Vendure and the plugin's page is in the admin. Without a key it runs in the free tier; open the page and click Start 14-day free trial to switch everything on — the key installs itself, no .env edit, no redeploy.

HTTP endpoints

Every route exposed.

GET/email-track/open/:id.gifPixel — logs an open then serves a 1×1 GIF
GET/email-track/click/:id?u=<url>&s=<sig>Click redirector — verifies HMAC signature, logs event with per-link metadata + classification, then 302s
POST/email-track/bounceBounce / complaint webhook — DSN bridge
POST/email-events/webhook/postmarkPostmark event webhook (Basic Auth verified)
POST/email-events/webhook/sendgridSendGrid event webhook (ECDSA signature verified)
POST/email-events/webhook/mailgunMailgun event webhook (HMAC-SHA256 verified)
POST/email-events/webhook/sesAmazon SES via SNS (topic ARN allowlist)
GET/email-track/logAdmin: paginated log with filters
GET/email-track/log/summaryAdmin: status totals tile
GET/email-track/log/:idAdmin: full detail (incl. opens + clicks arrays)
GET/email-track/log/stats/by-templateAdmin: per-template aggregates (open / click rate, CTR)
GET/email-track/log/export.csvAdmin: CSV export
GET/email-track/suppressionAdmin: list suppression entries
POST/email-track/suppressionAdmin: manually add a recipient
DELETE/email-track/suppression/:recipientAdmin: lift a suppression
GET/email-track/licence/statusAdmin: licence + evaluation + update status
POST/email-track/licence/activateAdmin: activate a licence key from the admin UI
POST/email-track/update/runAdmin: one-click in-app update + graceful restart

FAQ

Common questions.

How do I get a licence key?

Buy here — Stripe Checkout — monthly, annual (two months free) or lifetime. You'll receive the JWT key by email. Paste it into the plugin's admin settings (Activate) — no redeploy — or set it as HULO_LICENCE_KEY_EMAIL_TRACKING in your .env if you prefer config-as-code; the env key wins when both are present.

Does it work without a key?

Yes — every subscription starts with a 14-day free trial. Install the plugin, open its admin page and click Start 14-day free trial: a card is required, nothing is charged until day 15, and you can cancel any time before then. The licence installs itself and every premium feature is on for the whole trial with your real traffic.

Which databases are supported?

MySQL, MariaDB and PostgreSQL (verified against PostgreSQL 17). The plugin follows your Vendure dbConnectionOptions automatically — there is nothing to configure.

How do updates work?

The plugin checks the npm registry daily. When a newer version exists, the admin dashboard shows an update banner with a What's-new link to the changelog and an "Update now" button that installs the registry-verified release via your own package manager and gracefully restarts under your process supervisor. Prefer manual control? Copy the install command instead, or set HULO_SELF_UPDATE=off.

Where is data stored?

In your Vendure database. The plugin adds its own tables (created on boot, or via a migration for the plugins that register entities) — your data never leaves your server.

Will it survive a Vendure upgrade?

Tested against Vendure >=3.5.0 <4.0.0 — 3.5, 3.6 and 3.7 are all covered by CI. A boot-time compatibility check emits a non-fatal warning if @vendure/core is outside that range, so upgrades to a future 3.x are safe to try. The 4.0 line will be tested and re-declared once its changelog lands.

Ready to ship?

Install in five minutes, run the trial on real traffic, keep it if it earns its place.