Skip to content

Email campaigns and tracking

An email campaign is a tracked bulk email: a named message with a subject, sender, template, HTML body, and a recipient list, sent to many contacts with per-recipient open and click tracking. The same campaign engine also powers the platform's automated emails — the daily digest, deal emails, document-approval notifications, sign-off emails, spend reminders, RFPs, and survey invites all create campaigns behind the scenes.

The campaign UI (/campaign/list) is for composing and sending campaigns by hand and reviewing their results.

What a campaign is

A campaign record (emailCampaign table) holds a name, subject, from name / reply-to email, a template, and the HTML body. Its recipients live in a separate table (emailCampaignRecipient), and every open/click is logged to an activity table. Campaigns are not linked to an originating object in the database — an automated campaign knows nothing of the deal or document that triggered it beyond its content.

Building the recipient list

Recipients are added three ways:

  • Directly — pick individual contacts, or a group (which expands to its member contacts).
  • From a saved segment — the recipient builder picks a saved contact query, which runs its stored SQL to materialize the matching contacts.
  • Automated campaigns build recipients from tagged contacts/companies.

Sending

From the campaign editor you can send a test (to yourself only), save, or send for real to all recipients. Sending spawns a background job that, per recipient, personalizes the body (name, company, unsubscribe and view-in-browser links), rewrites links for click tracking, renders the template, and queues the email; it then marks the campaign sent and re-indexes it.

The provider integration is SendGrid-shaped (an X-SMTPAPI header carries the campaign and contact IDs so the provider echoes them back on tracking webhooks), sent over SMTP via the mail service.

Tracking and suppression

  • Clicks — every link in the body is rewritten to route through /api/campaign/rd, which logs a click and redirects to the real URL.
  • Opens — recorded when the recipient opens the "view in browser" link and via the provider's open webhook (logged as a read).
  • Delivery status — the provider's webhook updates each recipient's processed / delivered / bounced flags.
  • Suppression — a bounce marks the contact emailBlocked, so future campaigns skip that address. This is the platform's suppression list.

Aggregate counts (recipients, reads, clicks) are computed from the activity log and shown on the campaign list and detail views.

Public tracking endpoints

Three campaign endpoints are public/whitelisted so they work for recipients who aren't logged in: the click redirect (campaign.rd), the view-in-browser/open tracker (campaign.view), and the provider webhook (campaign.webhook) that posts delivery/open/click events. The webhook processes events asynchronously and identifies the campaign and contact from the IDs echoed in the message.

Who can create and send campaigns

The campaign endpoints require a logged-in user with the view permission. The detail view marks edit/delete as superuser-only, but those are advisory UI flags — the send, save, and delete endpoints do not enforce a role server-side.

Why do campaign stats change unexpectedly

  • Open/click counts rose after sending — recipients opened the email or clicked tracked links; both log asynchronously.
  • A contact stopped receiving campaigns — a bounce set their emailBlocked flag; they are now suppressed.
  • Counts differ between the list and a fresh reindex — aggregate counts are mirrored into the search index at index time; re-index a campaign to refresh them.

What email campaigns are not

  • Not the notification system — in-app notifications are separate.
  • Not the email inbox — that is a different feature.
  • Not per-deal — an automated campaign carries no database link back to the deal/document that triggered it.