Email comment notifications to doc owner and thread participants#2
Email comment notifications to doc owner and thread participants#2rgarcia wants to merge 4 commits into
Conversation
Sibling of the share notification. On a new comment, best-effort email the people who should hear about it, each with their own single-use 7-day share-kind login link to /d/:slug: - top-level comment (parent_id null): the document owner. - reply: the owner plus the thread's other participants, deduped. - always exclude the comment's author. Two layouts inside the locked man-page email style: top-level shows the anchored passage (when present) above the body; replies show minimal parent context. The footer's "why am I getting this" line keys off owner vs participant. Suppression uses a dedicated cmt-notify:addr daily cap so comment volume never burns the recipient's login/share email budget. The send is wired into POST /comments after the comment commits and surfaces a notified count on the 201 response; a send failure rolls back only the just-minted token row and never fails the request. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reply recipients were derived purely from comment-authorship history, so a thread participant who had since lost access (grant revoked, or doc flipped from public to private) kept receiving reply notifications — leaking thread activity and other participants' email addresses to someone who can no longer view the doc. Filter each non-owner participant through a live access check (public, or an owner/email/domain grant) before notifying; the owner is always notified. Also scope the reply parent-context lookup by doc_id and deleted_at IS NULL to match the rest of the comments module. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
🤖 Pulumi Neo didn't review this pull request: no Pulumi preview ran for it. Agentic reviews require a preview of the affected stacks (for example from your CI's |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Firetiger deploy monitoring skipped This PR didn't match the auto-monitor filter configured on your GitHub connection:
Reason: PR lacks repository context; cannot confirm whether this affects the To monitor this PR anyway, reply with |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Outer catch zeroes notified count
- I moved
notified/recipientscounters outside the main try block so the outer catch now returns the accurate partial send count instead of resetting to zero.
- I moved
- ✅ Fixed: Stale public flag skips access filter
- The comments POST flow now re-reads the document before sending notifications so recipient filtering uses current visibility when applying access checks.
Or push these changes by commenting:
@cursor push e9e1ca2726
Preview (e9e1ca2726)
diff --git a/app/api/v1/docs/[slug]/comments/route.ts b/app/api/v1/docs/[slug]/comments/route.ts
--- a/app/api/v1/docs/[slug]/comments/route.ts
+++ b/app/api/v1/docs/[slug]/comments/route.ts
@@ -139,7 +139,8 @@
// is already committed, so a send failure or tripped cap never fails the
// request. Notifies the owner (top-level) or the owner + thread participants
// (replies), minus the author.
- const { notified } = await sendCommentNotification({ req, doc, comment: result.comment });
+ const notifyDoc = (await findBySlug(slug)) ?? doc;
+ const { notified } = await sendCommentNotification({ req, doc: notifyDoc, comment: result.comment });
return json({ comment: commentView(result.comment, []), notified }, 201);
}
diff --git a/lib/docs/comment-notify.ts b/lib/docs/comment-notify.ts
--- a/lib/docs/comment-notify.ts
+++ b/lib/docs/comment-notify.ts
@@ -127,10 +127,13 @@
doc: DocRow;
comment: CommentRow;
}): Promise<CommentNotifyResult> {
+ let notified = 0;
+ let recipientsCount = 0;
try {
const { req, doc, comment } = opts;
const recipients = await resolveRecipients(doc, comment);
- if (recipients.length === 0) return { notified: 0, recipients: 0 };
+ recipientsCount = recipients.length;
+ if (recipientsCount === 0) return { notified: 0, recipients: 0 };
const isReply = comment.parent_id !== null;
const title = doc.title || doc.slug;
@@ -166,7 +169,6 @@
const next = `/d/${encodeURIComponent(doc.slug)}`;
const docUrl = `${ORIGIN}${next}`;
- let notified = 0;
for (const r of recipients) {
const to = r.email.toLowerCase();
@@ -220,9 +222,9 @@
notified += 1;
}
- return { notified, recipients: recipients.length };
+ return { notified, recipients: recipientsCount };
} catch {
// Best-effort: a comment notification must never fail the comment write.
- return { notified: 0, recipients: 0 };
+ return { notified, recipients: recipientsCount };
}
}You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 954bf1c. Configure here.
The owner has an account, so the share-grantee recovery copy ("no account
needed" / "was this shared with you? sign in") is wrong for them. Branch the
footer so owners are told to sign in normally and participants keep the
grantee-oriented copy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Re-read the document's current is_public at notification time so a public->private flip between the POST's initial doc load and the notification can't skip the participant access gate (which would email participants who no longer have access). Isolate each recipient and keep the notified count outside the try block, so a mid-loop failure (cap check or token mint) neither under-reports how many emails were sent nor aborts the remaining recipients. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>


Summary
Emails a notification when someone comments on a document, mirroring the existing share notification.
share-kind login token landing on/d/:slug, built exactly like the share email's verify URL so/login/verifyconsumes it unchanged. If it expires, the/d/:slug"was this shared with you? sign in" fallback recovers.cmt-notify:addr:<email>daily cap (30/day) — neverEMAIL_SEND_LIMITS— so comment volume can't burn a recipient's login/share email budget.notifiedcount.Wired into
POST /api/v1/docs/:slug/commentsafter the comment commits;CommentCreatedResponsegains anotifiedinteger (OpenAPI spec regenerated viagen:spec).Test plan
npm test— 99 passed (6 files). Addslib/docs/comment-notify.test.tscovering the recipient model (self-suppression, owner-only top-level, missing-owner drop, reply dedupe/author-exclusion), access-revocation drop, public-doc skip of the grants lookup, the dedicatedcmt-notifycap with theEMAIL_SEND_LIMITS-isolation guard, token/idempotency shape, snippet truncation, anchored-quote and parent-context surfacing, send-failure rollback, and the audit event.npm run spec:check— OK; committed spec artifacts in sync, 25 endpoints agree across the generated spec, route handlers, and/llms.txt.npx tsc --noEmit— clean.Note
Medium Risk
Touches email delivery, login token minting, and access-gated recipient selection on the comment write path; failures are best-effort but wrong recipient logic could leak thread activity or emails to revoked grantees.
Overview
Adds best-effort email notifications when a comment is created, wired into
POST /api/v1/docs/:slug/commentsafter the comment commits. The 201 response now includesnotified(count of emails actually sent); OpenAPI/CommentCreatedResponseupdated accordingly.Recipients: top-level comments notify the document owner only; replies notify the owner plus other thread participants (deduped, author excluded). Reply participants are filtered by current access (
resolveAccesson private docs; re-readis_publicso a public→private flip doesn’t leak). Owner always notified when applicable.Delivery: new
sendCommentNotificationorchestrator mints per-recipient 7-dayshare-kind login tokens to/d/:slug(same verify flow as share emails), uses dedicatedcmt-notify:addr:<email>rate cap (30/day, notEMAIL_SEND_LIMITS), rolls back token on send failure, auditscomment_notification.sent. NewsendCommentEmailinlib/auth/email.tswith top-level vs reply layouts (anchored quote / parent context).Extensive unit tests in
lib/docs/comment-notify.test.ts.Reviewed by Cursor Bugbot for commit 550e8fa. Bugbot is set up for automated code reviews on this repo. Configure here.