Deduplicate CC/BCC when multiple clients receive the same email action

Objective

  • Fix a bug where CC and BCC recipients receive duplicate emails when one send action delivers one message per client in To.
  • Ensure each unique CC and BCC email address receives at most one email for that send action.
  • Preserve per-client To sends so each client still gets their own personalized email.
  • Use one deterministic recipient context for the shared CC/BCC copy so merge fields and recipient-scoped blocks render consistently.

Background

  • Product feedback reported duplicate emails when two clients are in To and the same agent is in CC. This is a confirmed bug in production sending — not a template misconfiguration.
  • Current behavior sends one outbound message per deduped To recipient and attaches the full CC and BCC list to each send. The send path in renderEmail.ts loops over To recipients and re-sends shared CC/BCC on every iteration with no cross-send deduplication.
  • Existing To deduplication already handles the case where the same contact appears multiple times on the work order or in multiple roles (dedupePeopleByIdentity / resolveContactIdentity). That behavior should remain unchanged.
  • Contact profile ccEmails are merged into the current send only and deduped within that single send. They do not prevent the same CC address from receiving duplicate emails across multiple To sends.
  • The same duplicate-CC/BCC pattern exists in sendManualEmail (activity / send-manual flows): it loops over toEmails and attaches the full ccEmails / bccEmails arrays on each iteration.
  • The intended tradeoff for the fix: shared CC/BCC recipients receive one personalized variant for the action rather than one copy per client. That is acceptable for standard FYI-style recipient behavior.

Confirmed repro (action-flow / template send):

  1. Client A and Client B are both in To
  2. Agent C is in CC
  3. Client A receives 1 email
  4. Client B receives 1 email
  5. Agent C currently receives 2 emails (bug)

Required behavior:

  1. Client A still receives 1 email
  2. Client B still receives 1 email
  3. Agent C receives 1 email

Related behavior that should remain unchanged:

  • One contact appearing twice in To through multiple roles should still receive only 1 email
  • Two clients plus an agent all in To should still result in 1 email each
  • Two different contact records sharing one email address is a separate deduplication question and is out of scope unless surfaced during QA

Scope

Backend

  • Action-flow / template sends: Production email sending lives in src/util/functions/emailBuilder/renderEmail.ts. sendProductionEmails maps over dedupedToPeople and calls sendToRecipient for each To person with shared dedupedCCPeople and dedupedBCCPeople. Entry: src/util/functions/actionFlows/handleEventTriggerdByBull.tsrenderEmailFromTemplate.
  • Manual sends: sendManualEmail in the same file loops over toEmails and attaches full ccEmails / bccEmails on each iteration. Entry points include src/routes/flowActionResults.ts and src/routes/emailBuilder.ts. The same cross-send CC/BCC dedup fix must apply here.
  • Existing To deduplication flows through getDedupedToPeopleForEmailTemplate, dedupePeopleByIdentity, and resolveContactIdentity in src/util/functions/actionFlows/recipientIdentity.ts. That logic should stay focused on per-recipient To deduplication and should not be changed to collapse multiple client sends into one.
  • Required fix: Deduplicate CC and BCC across the full send action, not just within an individual message. After all To sends are determined for one action, each unique CC and BCC email address should receive at most one queued message, keyed by normalized email (normalizeEmail).
  • Shared CC/BCC copy context: Render the single shared delivery using one deterministic To context:
  • First choice: A matching To recipient marked primary: true on the job’s people[] entry.
  • Fallback: The first client-role person in people[] order who is also among the deduped To recipients for this action (i.e. first matching entry in production To order derived from people[], not a separate sorted list).
  • BCC follows the same cross-send deduplication and shared-context rules as CC.
  • Recipient-scoped rendering should continue through the existing path — replaceVariablesBackend, buildEmail, and recipient-based block/condition evaluation — using the chosen shared context, not whichever To iteration runs first.
  • Profile-level ccEmails (src/models/contactSchema.ts) still participate in recipient assembly but must not cause duplicate shared sends when the same normalized address is already in CC or BCC for the action.

Frontend

  • No UI change expected. Template and manual-send recipient configuration stay as-is.

Done when

  • Scenario 3 repro (two clients To, agent CC): each client receives 1 email; agent receives 1 email (not 2).
  • Cross-send CC/BCC dedup applies per send action, keyed by normalized email, in both sendProductionEmails and sendManualEmail.
  • Shared CC/BCC copy uses people.primary when set on a matching To recipient; otherwise uses the first client-role To recipient in people[] order.
  • Existing To dedup for same contact / multiple roles is unchanged.

References

  • Slack thread
  • src/util/functions/emailBuilder/renderEmail.tssendProductionEmails, sendToRecipient, sendManualEmail, dedupePeopleByIdentity
  • src/util/functions/actionFlows/recipientIdentity.tsresolveContactIdentity, normalizeEmail
  • src/util/functions/actionFlows/handleEventTriggerdByBull.ts
  • src/routes/flowActionResults.ts — manual send
  • src/routes/emailBuilder.ts — manual send
  • src/models/contactSchema.tsccEmails

Please authenticate to join the conversation.

Upvoters
Status

Planned

Board
🏠

Main App

Date

2 days ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.