MailSignals

Dashboard

The extension answers "did they read it". The dashboard answers "why do you think so".

A web app in your own Firebase project that reads your history live and shows the whole of it: how much you sent, how much landed, when people read you, who actually answers, and, for every single event, the sentence that justifies the verdict.

148
Sent
61%
Open rate, confirmed
19%
Reply rate
2 h 40 m
median, first confirmed open
12
also delivered, unconfirmed
7
bounced
Headline figures over 30 or 90 days. A rate with nothing sent is undefined, not zero. Apple prefetches appear as "also delivered, unconfirmed", never inside the open rate.
Confirmed opens by weekday and hour, in your timezone. Only counted opens feed it; a scanner's fetch would make it a map of when gateways run. The best-time callout is worded as an observation.

Seven pages, seven questions

Dashboard pages and the question each answers
PageThe question it answers
OverviewHow much did I send, how much landed, when, and what just happened
EmailsThe full history, filtered by search, status, mailbox and sent date; the 500 most recent matches, and it says so when the cap is hit
Email detailEvery recorded hit, with the sentence explaining the verdict and who was behind it
ContactsWho actually reads me: sorted by open rate, anyone with fewer than three sends greyed out, rates with no denominator sorted last
LinksWhich links were clicked, by a person or a bot, in separate columns; rows with only bot traffic dimmed
AlertsWhat was worth interrupting for: unread, all, snoozed
SettingsLinked Gmail accounts, every knob, agent keys, export and deletion

The detail page

The product's actual differentiator lives here.

  • The reason is body text, not a tooltip and not a disclosure. If a verdict cannot be justified in one readable line, it should not be shown.
  • Each entry: the classification badge, with "N% sure" only when confidence is below 85%; the verb (Pixel loaded, Link clicked, Reply received, Delivery failed); the time and delay since send; the reason; the destination for clicks; device, family and truncated network; and a "Who" line in plain words.
  • Any open or click not already judged yours can be claimed as your own, and undone. With two or more proxied opens, one button claims them all.
  • After two claims, a card explains that you read your sent mail on a device without MailSignals and points at Gmail's external-images setting.
  • The Tracked links panel distinguishes link tracking off at send, on but nothing eligible, and N links left as written, with the reason.
  • A multi-recipient message carries the note that an open tells you someone read it, not which one.
  • Header actions: Open in Gmail, Mute, and "Closed by you · Reopen" when you archived the thread.
  1. +3 s Delivered, open unconfirmed · 80% sure Pixel loaded Apple Mail Privacy Protection downloaded the image on delivery. The message reached the device, but we cannot tell whether it was read. Who: Nobody yet: the recipient's mail app fetched it on delivery.
  2. +26 min Opened Link clicked A mail client downloaded the image directly (Safari on iPhone), which happens when someone displays the message. Who: The recipient, on their own device.
  3. +3 d Delivery failed Delivery failed Gmail could not deliver to cc@oldvendor.example: Address not found. Who: Gmail, reporting that it could not deliver.
Three entries from one message with two recipients: a prefetch that is not a read, a click that is, and a bounce for the second address that does not undo the first recipient's click.

Reads are live

Every list is a live Firestore subscription, so a pixel hit appears within a second or two with no polling and no refresh. Every query is scoped to your account by path or by clause, mirroring the security rules, so a mistake shows up as a permission error rather than as someone else's data.

Writes go through the API

Every change goes through the same validated API the extension uses; the only direct writes are the read and snooze flags on your own alerts. Settings edits are held in a local draft and written in one request, so a half-typed number never reaches the server, and a live update never wipes out what you are typing.

Nothing third-party

The page loads no fonts and no analytics. Firebase and the charting library are split into their own chunks and cached for a year, so a redeploy does not invalidate the vendor code in everyone's cache. Errors are shown with a reload button and reported nowhere.

Export and delete

Messages, events, contacts and links export as CSV or JSON, capped at 5,000 messages, 20,000 events, 5,000 contacts and 5,000 links, with the JSON reporting a truncation flag rather than cutting off silently. CSV is RFC 4180 with CRLF line endings, and any cell beginning with a character that would make a spreadsheet execute it is guarded: an export contains recipient-controlled strings.

"Delete everything" requires typing the phrase, re-checked on the server, and removes every message, event, link, contact, statistic and alert. It keeps the account, its settings, its linked mailboxes and its agent keys: this is "start again", not "close account".

Where it lives

dashboard/src/pages/EmailDetail.tsx
The timeline, the reason as body text, the Who line, claims and undo.
dashboard/src/lib/metrics.ts
Status at your sensitivity, the funnel, the heatmap, contact rows.
dashboard/src/lib/data.ts
Every read, as a live subscription.
firestore.rules
The real read boundary, tested as a real end user by the security suite.

Related: the verdicts · what is stored, and export and deletion in full.

Run it in your own project.

One Firebase project, two subdomains, one deploy script. About an hour the first time, and no subscription afterwards.