feat(notification): notify followers when a bot publishes a post - #234
Merged
Conversation
Following the TypeScript bot did nothing for you: TypeScript would ship a release, the bot would post it, and the only way to find out was to open the feed and scroll. `NotificationType.NEW_POST` was already in the enum and `Notification` already knew how to render one, but nothing in the codebase ever created one - `CreatePostUseCase` had no notification or realtime dependency at all. The skeleton was there and the fan-out was never written. Only the bot-authored types notify: TECH_NEWS and SYSTEM_UPDATE. Community posts stay silent, because the ~354 persona accounts that keep the feed from looking empty publish those too, and notifying every follower of every one of them would bury the releases people actually followed an account for. Of the last 50 posts in production, 49 were bot-authored and 1 was community, so this covers essentially all of the signal and none of the noise. The fan-out lives in its own use case rather than inside post creation, and runs after the post commits with its failure logged instead of thrown - the post is the thing worth keeping, so a notification problem must not surface as a failed request. That shape is also what makes the eventual move onto a queue a change of caller rather than a rewrite: today `CreatePostUseCase` calls it, tomorrow a worker does, and the use case does not know the difference. `createMany` deliberately skips the per-recipient history trim that `create` performs. That trim costs two extra queries per recipient, which would turn a fan-out into 2N+1 queries and defeat the batch; the purge job already bounds notifications by age. `getFollowerIds` leaves out soft-deleted followers, so a deleted account cannot collect notifications while it waits to be purged. Also removes a stray factory fragment that had been pasted inside the use-cases DI module docblock. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HWgtHjp6QQe4A7WfJXNknR
github-actions Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
# [1.12.0](v1.11.2...v1.12.0) (2026-08-28) ### Features * **notification:** notify followers when a bot publishes a post ([#234](#234)) ([772d00f](772d00f))
|
🎉 This PR is included in version 1.12.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
I follow the
typescriptbot. TypeScript ships a release, the bot posts it, and nothing reaches me — the only way to find out is to open the feed and scroll.NotificationType.NEW_POSTis already in the enum, andNotificationalready has anisNewPost()helper and a message case for it. But nothing in the codebase ever creates one:CreatePostUseCasehad no notification or realtime dependency at all. The skeleton was there; the fan-out was never written.Scope: bot posts only
TECH_NEWSandSYSTEM_UPDATEnotify followers.COMMUNITYdoes not.The ~354 persona accounts that keep the feed from looking empty are also
isBotand publish community posts; notifying every follower of every one of those would bury the releases people actually followed an account for. The production numbers say this costs almost nothing in coverage — of the last 50 posts, 49 were bot-authored and 1 was community.Shape
The fan-out is its own use case (
NotifyNewPostUseCase) rather than logic inside post creation, called after the post commits with its failure logged instead of thrown:The post is the thing worth keeping, so a notification problem must not surface as a failed request. That shape is also what makes the eventual queue migration a change of caller rather than a rewrite — today
CreatePostUseCasecalls it, tomorrow a worker does, and the use case cannot tell the difference. Tracked in #235.Sizing that decision, from production: the largest bot has 102 followers,
typescripthas 3, and bots publish ~49 posts/day across 144 accounts (roughly one per bot every three days). A batched fan-out is ~2 queries plus one Redis publish per recipient — a queue today would be buying retry/dead-letter/worker-deployment overhead for a problem that does not exist yet.Two deliberate deviations worth reviewing
createManydoes not trim recipient history.create()runs a per-recipient 100-row trim after every insert (afindManyplus adeleteMany). Repeating that in a batch would make a fan-out2N+1queries and defeat the point. Age is already bounded by the notification purge job. If the 100 cap should be maintained, it belongs in that job as a bulk operation — noted, not done here.getFollowerIdsexcludes soft-deleted followers, so an account awaiting purge cannot accumulate notifications it will never read.Also
Removes a stray factory fragment that had been pasted inside the use-cases DI module docblock (
use-cases.di.ts, lines 79-84) — harmless because it sat in a/** */, but it corrupted the module's documentation.Verification
pnpm build,pnpm lint,pnpm format:check— cleanpnpm test:unit— 855/855getFollowerIdsdirection, soft-delete exclusion, andcreateManywriting theNEW_POSTfields and counting toward the unread badgetests/e2e/notification/— 29/29 andtests/e2e/post/— 24/24The new E2E walks the actual reported scenario end to end: a user follows the seeded bot, the bot publishes
TECH_NEWSunder its own token, and the notification appears with the rightpostIdwhile the unread count rises. It also pins the negatives — a non-follower gets nothing, and aCOMMUNITYpost produces nothing. The fan-out is deliberately asynchronous, so the assertion polls rather than assuming it has landed by the time the post response returns.🤖 Generated with Claude Code
https://claude.ai/code/session_01HWgtHjp6QQe4A7WfJXNknR