You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add secure click analytics for campaign emails and one-time emails by rewriting eligible links through a first-party redirect endpoint, recording the click, and then redirecting the recipient to the original destination.
This is the follow-up to #111 and should be implemented after the open-tracking foundation in #112 is merged. Click tracking was intentionally left out of that work so the redirect security, attribution rules, and reporting semantics can be reviewed independently.
Motivation
Open events are an approximate engagement signal because images can be blocked, proxied, or prefetched. Clicks provide a more useful indication that a recipient interacted with the email and allow administrators to answer questions such as:
How many delivered recipients clicked at least one link?
Which links received the most engagement?
When did a recipient first and most recently click?
How many total clicks did a campaign or recipient generate?
Definitions
Unique clicker: a sent queue recipient with at least one recorded click.
Total clicks: all recorded click requests, including repeat clicks by the same recipient.
Click-through rate (CTR): unique clickers divided by sent recipients.
Click-to-open rate (CTOR): unique clickers divided by unique opens. If shown, it must be clearly labeled and handle a zero-open denominator.
Per-link unique clicks: recipients who clicked a specific logical link at least once.
Proposed flow
Render the final email body using the existing wrapper and subscriber placeholder pipeline.
Find eligible HTML anchor links and replace their destinations with signed Mail System tracking URLs.
Include a separate, unguessable per-queue click token plus a stable link identifier in each tracking URL. Do not reuse the open-pixel token as the complete click credential.
When the endpoint receives a valid request, atomically insert/update click statistics for the queue recipient and logical link.
If that recipient does not yet have an open timestamp, record an inferred open at the click time: a recipient can click even when their email client blocked the tracking image.
Return a non-cacheable 302 redirect to the validated original URL.
The rewrite must run for:
Campaign messages sent by the cron queue.
Scheduled one-time emails.
Immediate one-time emails.
It should run after header/footer rendering and subscriber placeholder replacement so personalized destinations are preserved, but before the message is handed to wp_mail().
Link rewrite rules
Rewrite only absolute http:// and https:// anchor destinations.
Do not rewrite:
mailto:, tel:, sms:, fragment-only, data:, or javascript: links.
Unsubscribe and subscription-confirmation links.
The open-tracking pixel URL or non-anchor resources such as images.
Links explicitly opted out with data-mskd-no-track.
Invalid or unsupported URLs.
The implementation must preserve existing query strings, fragments, link text, surrounding markup, and other anchor attributes. Repeated destinations should retain a stable per-message logical link identity so they can be grouped consistently in reporting.
The parser must remain compatible with the plugin's supported WordPress 5.0 and PHP 7.4 minimums. Do not depend solely on newer WordPress HTML APIs without a compatible fallback.
Security and privacy requirements
Sign the redirect payload with an HMAC using WordPress salts. The signature must cover the queue token, logical link identifier, and exact redirect destination.
Use constant-time signature comparison and reject tampered, expired/invalid, unknown, or unsupported-scheme payloads.
Validate the decoded destination immediately before redirecting. This endpoint must not become a generic open redirect.
Return an error without redirecting or recording a click for invalid payloads.
Use a 302 response with cache prevention and Referrer-Policy: no-referrer.
A HEAD request may follow the redirect but must not increment analytics; count valid GET requests only.
Do not store IP addresses, user-agent strings, or device/location fingerprints.
Avoid persisting personalized query-string values or other subscriber PII in the reporting label. A sanitized display URL/label can omit the query string and fragment while the exact destination remains only in the signed redirect payload.
Document that security scanners and link-prefetching services can create false-positive clicks. Bot classification is not required for the first version.
Attribution and BCC handling
The current send paths can attach a BCC recipient to a message whose body is personalized for the primary To recipient. A BCC recipient clicking the same tracked URL must not be attributed to the primary recipient.
The implementation must choose and test a safe behavior, for example:
Send BCC copies with untracked links; or
Disable click tracking on any copy delivered through BCC and document that limitation.
It is not acceptable for a BCC click to modify the primary queue recipient's open or click statistics.
Data model
Add an idempotent schema migration following the database version introduced by #112. The exact table/column names may follow the project's conventions, but the stored model must support:
Queue recipient ID.
Stable logical link key/index.
Sanitized link label or display destination.
Immutable first-click timestamp.
Last-click timestamp.
Total click count.
A uniqueness constraint for one aggregate row per queue recipient and logical link.
Efficient indexes for recipient, campaign, link, and timestamp reporting.
Repeated clicks must update last_clicked_at and increment the count without changing first_clicked_at. Concurrent requests must not create duplicate aggregate rows or lose increments.
Include appropriate uninstall cleanup and define how click data follows the existing email-statistics retention behavior.
Admin analytics
Extend the queue overview and campaign detail reporting with:
Unique clickers.
Total clicks.
CTR based on sent recipients.
CTOR if included, clearly separated from CTR.
Opened/clicked recipient filters.
Per-recipient first click, last click, and total click count.
Per-link performance: sanitized link label/destination, unique clickers, total clicks, and click rate.
The UI should state that click counts are approximate because automated security scanners may visit links before a human does.
Acceptance criteria
Eligible links are rewritten for campaign, scheduled one-time, and immediate one-time emails.
Unsupported, unsubscribe/confirmation, tracking-resource, and opted-out links remain unchanged.
Existing URL query strings/fragments, placeholders, anchor attributes, and email markup are preserved.
Tracking URLs use an unguessable queue credential and a tamper-evident signed destination.
Invalid or modified payloads neither redirect nor record analytics.
Valid GET requests record the click once per request and redirect with a non-cacheable 302 response.
First/last click timestamps and repeat-click counts remain correct under retries and concurrent requests.
A valid click records an inferred open when no open exists, without overwriting an existing open timestamp.
BCC recipients cannot create open or click events attributed to the primary recipient.
Queue and campaign views expose unique clickers, total clicks, CTR, recipient details, and per-link results.
Reporting labels do not persist personalized URL parameters or other subscriber PII.
No IP address, user-agent, device, or location data is stored.
Schema installation, upgrades, retention, and uninstall cleanup are covered.
User-facing strings are translatable and documentation/changelog entries explain behavior and limitations.
Test coverage
Add automated coverage for at least:
Multiple links, repeated destinations, quoted attributes, HTML entities, personalized placeholders, and mildly malformed email HTML.
Every excluded scheme/type and the explicit opt-out attribute.
Unsubscribe and confirmation link exclusions.
Signature tampering, unknown queue tokens, invalid destinations, unsupported schemes, HEAD, and repeated GET requests.
Atomic first/last/count updates and inferred-open behavior.
Campaign, scheduled one-time, and immediate one-time render/send integration.
BCC behavior and attribution isolation.
Recipient, campaign, and per-link aggregation queries, including zero denominators.
Compatibility with the documented WordPress and PHP minimum versions.
Summary
Add secure click analytics for campaign emails and one-time emails by rewriting eligible links through a first-party redirect endpoint, recording the click, and then redirecting the recipient to the original destination.
This is the follow-up to #111 and should be implemented after the open-tracking foundation in #112 is merged. Click tracking was intentionally left out of that work so the redirect security, attribution rules, and reporting semantics can be reviewed independently.
Motivation
Open events are an approximate engagement signal because images can be blocked, proxied, or prefetched. Clicks provide a more useful indication that a recipient interacted with the email and allow administrators to answer questions such as:
Definitions
Proposed flow
302redirect to the validated original URL.The rewrite must run for:
It should run after header/footer rendering and subscriber placeholder replacement so personalized destinations are preserved, but before the message is handed to
wp_mail().Link rewrite rules
Rewrite only absolute
http://andhttps://anchor destinations.Do not rewrite:
mailto:,tel:,sms:, fragment-only,data:, orjavascript:links.data-mskd-no-track.The implementation must preserve existing query strings, fragments, link text, surrounding markup, and other anchor attributes. Repeated destinations should retain a stable per-message logical link identity so they can be grouped consistently in reporting.
The parser must remain compatible with the plugin's supported WordPress 5.0 and PHP 7.4 minimums. Do not depend solely on newer WordPress HTML APIs without a compatible fallback.
Security and privacy requirements
302response with cache prevention andReferrer-Policy: no-referrer.HEADrequest may follow the redirect but must not increment analytics; count validGETrequests only.Attribution and BCC handling
The current send paths can attach a BCC recipient to a message whose body is personalized for the primary
Torecipient. A BCC recipient clicking the same tracked URL must not be attributed to the primary recipient.The implementation must choose and test a safe behavior, for example:
It is not acceptable for a BCC click to modify the primary queue recipient's open or click statistics.
Data model
Add an idempotent schema migration following the database version introduced by #112. The exact table/column names may follow the project's conventions, but the stored model must support:
Repeated clicks must update
last_clicked_atand increment the count without changingfirst_clicked_at. Concurrent requests must not create duplicate aggregate rows or lose increments.Include appropriate uninstall cleanup and define how click data follows the existing email-statistics retention behavior.
Admin analytics
Extend the queue overview and campaign detail reporting with:
The UI should state that click counts are approximate because automated security scanners may visit links before a human does.
Acceptance criteria
GETrequests record the click once per request and redirect with a non-cacheable302response.Test coverage
Add automated coverage for at least:
HEAD, and repeatedGETrequests.Likely implementation areas
includes/services/class-mskd-cron-handler.phpincludes/Admin/class-admin-email.phpincludes/services/class-email-tracking-service.phpincludes/services/class-email-service.phppublic/class-mskd-public.phpincludes/class-activator.phpadmin/partials/queue.phpadmin/partials/queue-detail.phpuninstall.phpOut of scope