Repository navigation
feat(pulse): per-brand Daily Pulse digest with highlights, warnings and email/webhook delivery - #561
Merged
Merged
Conversation
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.
Summary
Implements Daily Pulse (#540): a per-brand digest generated right after the daily tracking run — good news and warnings in one email instead of an alerts-only bad-news machine.
Content (
server/src/lib/pulse/metrics.js) — computed through the same RPCs the Insights dashboard uses (visible_prompt_stats,tracked_prompt_count,insights_aggregates,competitor_aggregates,prompt_performance_aggregates) with the same rounding, so the email always matches the dashboard:prompt_target_urls.first_cited_at), top 3 prompts by weekly visibility gain (≥5 pts), competitors overtaken on the leaderboard, first appearance on a new answer engineDelivery (
server/src/lib/pulse/engine.js):isCloud()gate), with aList-Unsubscribeheader and a settings deep link. Recipients: explicit per-brand list, or every org member by default.daily_pulse.createdwebhook event on cloud and self-host alike, through a new shareddispatchBrandWebhook()helper extracted from the Content routes (same flat payload +X-Webhook-Secretformat).active/trialingorgs, active brands, and only when the window has fresh results. One pulse per brand per day, race-safe via aUNIQUE(brand_id, pulse_date)claim. Warnings repeat at most once per subject per 7 days (warning_keyscooldown).job-runner.jsafter a completed non-immediate tracking job — manual runs and single-prompt refreshes never send pulses. A pulse failure can never fail the tracking job.Settings — new Settings → Notifications section: per-brand frequency (Daily / Weekly / Only when notable / Off, default Daily) and recipient list, admin/manager writable, backed by
GET/PUT /api/settings/pulse. Weekly pulses run the same engine over a 7-day window and send on Mondays.Data model — migration
00036_daily_pulse.sql:pulse_settings+sent_pulseswith member-select RLS (same shape astopic_suggestions). Already applied to the hosted database and verified live.Also: README "Anomaly alerts" TODO updated to reference this feature, CHANGELOG entry, webhook docs updated with the new event,
server/.env.examplegainsRESEND_API_KEY/RESEND_FROM_EMAIL/PUBLIC_APP_URL, and a manual trigger script (node src/scripts/test-pulse.js <brandId>).Related issue
Closes #540
Type of change
feat— New featurefix— Bug fixchore— Maintenance / dependenciesdocs— Documentation onlyrefactor— Code change that neither fixes a bug nor adds a featuretest— Adding or updating testsHow to test
RESEND_API_KEY,RESEND_FROM_EMAILandPUBLIC_APP_URLinserver/.env(cloud mode:IS_CLOUD=true).node src/scripts/test-pulse.js <brandId>fromserver/for a brand with fresh results — an email arrives for each org member, asent_pulsesrow is written, and re-running skips withalready_sent.Checklist
feature/,fix/,chore/,docs/) — see CONTRIBUTING.mdyarn lintpasses (run fromweb/)yarn typecheckpasses (run fromweb/)yarn format:checkpasses (run fromweb/)