Skip to content

[DT-4263][2/7] Add an email sends endpoint for the Admin Console email log - #3142

Open
kevinmarete wants to merge 9 commits into
km-dt-4263-email-send-idfrom
km-dt-4263-email-sends
Open

kevinmarete wants to merge 9 commits into
km-dt-4263-email-send-idfrom
km-dt-4263-email-sends

Conversation

@kevinmarete

@kevinmarete kevinmarete commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Addresses

https://broadworkbench.atlassian.net/browse/DT-4263

Security risk: low — a new admin-only read endpoint; it adds recipients' display names to data /api/mail/range already returns to admins.

Summary

Each recipient of an email gets its own email_entity row, so the Email Log shows a digest sent to 87 people as 87 rows. GET /api/mail/sends returns one entry per send instead, with its recipient count and names, for the grouped Email Log agreed on DT-4084.

  • Stacked on [DT-4263][1/7] Record a send id on each email row #3149, which records a send_id per request. A send is the rows sharing a send_id, email type and entity reference. Rows written before [DT-4263][1/7] Record a send id on each email row #3149 have no send_id, so for them a send is the rows of one type and reference each created no more than 10 minutes after the previous one.
  • Same start, end, limit (capped at 1000) and offset as /api/mail/summary, paged by send, newest first. Each send lists up to 100 recipients by name, each marked sent or not (SendGrid accepted it; nothing records inbox delivery); recipientCount counts them all.
  • /api/mail/summary is unchanged: the deployed Email Log page still reads it, until part 3 moves the page to /sends.

Reading the query

  • in_range: the rows in the date range. lone_id is set only on an older row with neither a send_id nor an entity reference, so it partitions alone.
  • flagged: per type, reference, lone_id and send_id, starts_send = 1 on the first row. Rows with a send_id never start another; older rows do after a gap of more than 10 minutes.
  • numbered: a running sum of starts_send, so every row of one send shares a send_number.
  • grouped: each row gets send_row_id, the smallest email id in its send, which identifies the send from here on.
  • page: one row per send with its time and distinct recipient count, then this request's OFFSET/LIMIT.
  • named: the page's rows joined to users, one row per recipient, ordered by display name, sent if SendGrid accepted any of their copies.
  • The final SELECT builds each send's recipients from its first 100 names.

Example: two NEW_STUDY_DIGEST rows from one daily job share a send_id 30 minutes apart, so they're one send. Two NEW_DAR rows for DAR-1 from different requests a minute apart have different send_ids, so they're two.

The default end is now the start of tomorrow in the server's time zone, the same zone explicit dates are read in; this also applies to /range and /summary, and changes nothing where the server runs in UTC.

The 10-minute cutoff hasn't been measured against dev data yet. A send that crosses the range's start or end is split there.


Have you read CONTRIBUTING.md lately? If not, do that first.

  • Label PR with a Jira ticket number and include a link to the ticket
  • Label PR with a security risk modifier [no, low, medium, high]
  • PR describes scope of changes
  • Get a minimum of one thumbs worth of review, preferably two if enough team members are available
  • Get PO sign-off for all non-trivial UI or workflow changes
  • Verify all tests go green
  • Test this change deployed correctly and works on dev environment after deployment

🤖 Generated with Claude Code

@kevinmarete kevinmarete self-assigned this Oct 9, 2026
@kevinmarete kevinmarete changed the title [DT-4263][1/3] Add an email sends endpoint for the Admin Console email log [DT-4263][1/4] Add an email sends endpoint for the Admin Console email log Oct 10, 2026
@kevinmarete
kevinmarete force-pushed the km-dt-4263-email-sends branch from 162bef3 to 7617930 Compare October 10, 2026 01:09
@kevinmarete
kevinmarete changed the base branch from develop to km-dt-4263-email-send-id October 10, 2026 01:09
kevinmarete and others added 7 commits October 9, 2026 21:21
…l log

Each recipient of an email gets its own email_entity row, so the email
log lists one send many times. GET /api/mail/sends groups rows of one
type and entity reference created within 10 minutes of each other into
a send, with its recipient count and names, paged by send.

/api/mail/summary is unchanged because the deployed Email Log page
still reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page of sends is chosen before recipients are joined to users and
aggregated, so each request only builds JSON for its own page. Each
send lists at most 100 recipients, ordered by name, while
recipientCount still counts them all. Also renames the shared page cap,
documents what sendId is, and gives the grouping test distinct names so
it checks the order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A recipient's second email of the same type and reference now starts a
new send, so emails re-sent every few minutes no longer merge into one,
and emails with no entity reference are each a send of their own. The
default end date now uses the server's time zone, like explicit dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Splitting sends per recipient occurrence misgrouped overlapping
batches; send ids recorded at write time will replace the heuristic, so
it goes back to the 10-minute gap. Each recipient now says whether
SendGrid accepted the email, and a send with no listed recipients is
kept rather than dropped from the page. Also pins a create-date test to
UTC, which failed between 8pm and midnight Eastern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A user with more than one email in a send is now listed and counted
once, delivered if any of their emails was. Also documents that
delivered is false everywhere when notifications are off, and drops a
redundant aggregate and a comment that restated a type name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rows that share a send_id, type and reference are now one send however
long it took to write them, and rows from different requests stay apart
however close together. Rows written before send ids keep the 10-minute
gap rule. Each send is identified by its first row instead of a made-up
key that could collide with a real entity reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An interface constant on the DAO is a Sonar warning (S1214), so the
cap now lives in EmailService and is always passed to the query. Also
covers the sends endpoint's default end and a negative offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kevinmarete
kevinmarete force-pushed the km-dt-4263-email-sends branch from 7617930 to 083c4ad Compare October 10, 2026 01:22
@sonarqubecloud

Copy link
Copy Markdown

kevinmarete and others added 2 commits October 9, 2026 21:56
The email log needs it to keep a send whose first email falls just
before the chosen range but whose others fall inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It comes from date_sent, set when SendGrid's API accepts the email;
consent records nothing about delivery to the inbox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kevinmarete
kevinmarete marked this pull request as ready for review October 10, 2026 05:04
@kevinmarete
kevinmarete requested a review from a team as a code owner October 10, 2026 05:04
@kevinmarete
kevinmarete requested review from fboulnois, otchet-broad and rushtong and removed request for a team October 10, 2026 05:04
@kevinmarete kevinmarete changed the title [DT-4263][1/4] Add an email sends endpoint for the Admin Console email log [DT-4263][2/7] Add an email sends endpoint for the Admin Console email log Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant