Skip to content

feat(notification): notify followers when a bot publishes a post - #234

Merged
aquie00t merged 1 commit into
mainfrom
feature/new-post-notifications
Aug 28, 2026
Merged

feat(notification): notify followers when a bot publishes a post#234
aquie00t merged 1 commit into
mainfrom
feature/new-post-notifications

Conversation

@aquie00t

@aquie00t aquie00t commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

The gap

I follow the typescript bot. 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_POST is already in the enum, and Notification already has an isNewPost() helper and a message case for it. But nothing in the codebase ever creates one: CreatePostUseCase had no notification or realtime dependency at all. The skeleton was there; the fan-out was never written.

Scope: bot posts only

TECH_NEWS and SYSTEM_UPDATE notify followers. COMMUNITY does not.

The ~354 persona accounts that keep the feed from looking empty are also isBot and 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:

void this.notifyNewPostUseCase
    .execute({ postId: rawPost.id, authorId: input.authorId, postType: input.type })
    .catch((err: unknown) => {
        this.logger.error({ err, postId: rawPost.id }, "Failed to notify followers of a new post");
    });

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 CreatePostUseCase calls 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, typescript has 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

createMany does not trim recipient history. create() runs a per-recipient 100-row trim after every insert (a findMany plus a deleteMany). Repeating that in a batch would make a fan-out 2N+1 queries 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.

getFollowerIds excludes 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 — clean
  • pnpm test:unit — 855/855
  • Integration (follow + notification repositories) — 32/32, covering getFollowerIds direction, soft-delete exclusion, and createMany writing the NEW_POST fields and counting toward the unread badge
  • E2E tests/e2e/notification/ — 29/29 and tests/e2e/post/ — 24/24

The new E2E walks the actual reported scenario end to end: a user follows the seeded bot, the bot publishes TECH_NEWS under its own token, and the notification appears with the right postId while the unread count rises. It also pins the negatives — a non-follower gets nothing, and a COMMUNITY post 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

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
@aquie00t
aquie00t merged commit 772d00f into main Aug 28, 2026
10 checks passed
@aquie00t
aquie00t deleted the feature/new-post-notifications branch August 28, 2026 09:27
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))
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.12.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant