feat: implement granular per-user email notification preferences#1308
Conversation
|
Warning Rate limit exceeded@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 9 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (31)
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughIntroduces granular per-user and per-organization email notification preferences system. Adds Changes
Sequence Diagram(s)sequenceDiagram
participant User as User<br/>(Frontend)
participant API as Supabase API
participant DB as Database<br/>(users/orgs)
participant PrefsSync as Preference Sync<br/>(syncUserPreferenceTags)
participant Bento as Bento
participant Cron as Cron Email<br/>Triggers
User->>API: Toggle email preference<br/>(e.g., weekly_stats)
API->>DB: Update email_preferences<br/>JSONB column
DB-->>API: Confirmed
API-->>User: Toast: Preference updated
Note over DB,PrefsSync: Background sync triggered
PrefsSync->>PrefsSync: Build preference segments:<br/>disabled tags for OFF prefs
PrefsSync->>Bento: Add/remove tags<br/>(addTagBento)
Bento-->>PrefsSync: Tags synced
Note over Cron: Later: Scheduled trigger fires
Cron->>DB: Fetch user email_preferences
DB-->>Cron: Preferences (e.g., {weekly_stats: false})
alt weekly_stats disabled
Cron-->>Cron: Skip email<br/>Return 200
else weekly_stats enabled
Cron->>Cron: Compute stats
Cron->>Bento: Track event<br/>(send email)
Bento-->>Cron: Event recorded
end
Note over User,Bento: Email delivery respects Bento tag filters<br/>(Tag does NOT contain disabled_weekly_stats)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Fetch user email_preferences separately since it might not be in generated types yet | ||
| const userIds = members.map(m => m.user_id) | ||
| const { data: users } = await supabaseAdmin(c) | ||
| .from('users') | ||
| .select('id, email') | ||
| .in('id', userIds) |
There was a problem hiding this comment.
Load email_preferences when filtering recipients
The preference filter never takes effect because the users query only selects id, email, so email_preferences is never returned and prefs[preferenceKey] is always undefined. That causes every admin to be treated as opted‑in, meaning opt‑outs for usage, deploy, onboarding, etc. are ignored for org notifications. Include email_preferences in the select (or fetch it via the join) so the filter actually respects user choices.
Useful? React with 👍 / 👎.
| // Get the org's management email for the notification table check | ||
| const { data: org, error: orgError } = await supabaseAdmin(c) | ||
| .from('orgs') | ||
| .select('management_email') | ||
| .eq('id', orgId) | ||
| .single() | ||
|
|
||
| if (!org || orgError) { | ||
| cloudlog({ requestId: c.get('requestId'), message: 'sendNotifToOrgMembers: org not found', orgId }) | ||
| return false | ||
| } | ||
|
|
||
| // Use sendNotifOrg to handle the notification table logic (throttling/deduplication) | ||
| // It will send to the org's management_email, but we also need to send to other eligible members | ||
| const orgEmailSent = await sendNotifOrg(c, eventName, eventData, orgId, uniqId, cron) | ||
|
|
There was a problem hiding this comment.
Avoid sending to management_email without eligibility checks
sendNotifToOrgMembers gates on sendNotifOrg, but sendNotifOrg always delivers to org.management_email (see notifications.ts) before you filter eligible members. If the management email is a billing alias, a non‑admin, or a user who disabled the preference, it will still receive operational alerts, which contradicts the “eligible members only” behavior introduced here. Consider separating the throttling logic from delivery or checking eligibility before sending to management_email.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/types/supabase.types.ts (1)
310-363: Keep frontend Supabase types in sync with backend schema (includingusers.email_preferences)This file mirrors the backend
supabase.functions/_backend/utils/supabase.types.tschanges for audit logs, webhooks, 2FA helpers, andstats_actionvalues, which is good for consistency.The same gap applies here:
public.usershas noemail_preferencesfield inRow/Insert/Update, even though the migration and tests treat it as an existing JSONB column. Frontend code using these types won’t see the column and may end up withanycasts or TS errors.Once the migration is finalized, please regenerate this type file from the database so
users.email_preferencesis properly typed and stays aligned with the backend types.Also applies to: 847-882, 1265-1304, 1877-2002, 2098-2104, 2110-2113, 2414-2466, 2581-2583, 2712-2712, 2777-2780, 2930-2931, 3182-3183
supabase/functions/_backend/utils/org_email_notifications.ts (2)
62-75: Critical: email_preferences not fetched, breaking opt-out functionality.The query on line 66 only selects
id, emailbut line 72 attempts to accessemail_preferences. This field is never fetched, soprefs[preferenceKey]on line 86 is alwaysundefined, causing all users to be treated as opted-in (line 87 defaults totrue). User opt-outs for usage alerts, deploy stats, onboarding, etc. are completely ignored.🔎 Fix: Include email_preferences in the query
const { data: users } = await supabaseAdmin(c) .from('users') - .select('id, email') + .select('id, email, email_preferences') .in('id', userIds)
188-207: Major: management_email bypasses eligibility and preference checks.
sendNotifOrg(line 202) delivers toorg.management_emailbefore verifying that email belongs to an eligible admin or respects user preferences. If the management email is a billing alias, belongs to a non-admin, or to a user who disabled this preference, they receive operational alerts anyway—contradicting the per-user preference system introduced in this PR.Consider checking eligibility and preferences for the management email before sending, or passing eligible recipient lists to
sendNotifOrg.
🧹 Nitpick comments (8)
tests/email-preferences.test.ts (1)
1-520: Strong end-to-end coverage for email_preferences; consider usinggetEndpointUrlfor cron triggersThe test suite exercises the new
email_preferencescolumn and its integration points well: default values, single/multi-key updates, toggling back to true, deploy/weekly/monthly cron_email behavior, and an org-user admin check. Skipping tests when the migration isn’t applied is a pragmatic guard for environments that lag behind schema.Two minor suggestions:
- For the HTTP calls to
/triggers/cron_email, prefer using thegetEndpointUrl('/triggers/cron_email')helper fromtest-utils.tsinstead of interpolatingBASE_URL, so these tests stay aligned with the worker/route configuration expected by the backend test harness.- Optional: the repeated
isMigrationApplied()check and “get current preferences then update” pattern could be factored into small helpers, but this is more about DRY than correctness.Behavior-wise, the tests look good and should catch regressions in the preference filtering logic.
supabase/functions/_backend/triggers/cron_email.ts (2)
11-21: Consider importingEmailPreferencesinterface instead of duplicating it.This
EmailPreferencesinterface duplicates the one defined insupabase/functions/_backend/utils/org_email_notifications.ts(lines 9-18). Importing the shared type would reduce maintenance burden and ensure consistency.🔎 Proposed fix
-import type { EmailPreferenceKey } from '../utils/org_email_notifications.ts' +import type { EmailPreferenceKey, EmailPreferences } from '../utils/org_email_notifications.ts' import { Hono } from 'hono/tiny' import { trackBentoEvent } from '../utils/bento.ts' import { BRES, middlewareAPISecret, parseBody, simpleError } from '../utils/hono.ts' import { cloudlog, cloudlogErr } from '../utils/logging.ts' import { readStatsVersion } from '../utils/stats.ts' import { supabaseAdmin } from '../utils/supabase.ts' -interface EmailPreferences { - usage_limit?: boolean - credit_usage?: boolean - onboarding?: boolean - weekly_stats?: boolean - monthly_stats?: boolean - deploy_stats_24h?: boolean - bundle_created?: boolean - bundle_deployed?: boolean - device_error?: boolean -}
33-37: Optimize query to select only the required column.The query selects all columns but only uses
email_preferences. Narrowing the selection improves performance and reduces data transfer.🔎 Proposed fix
const { data: user, error } = await supabaseAdmin(c) .from('users') - .select('*') + .select('email_preferences') .eq('email', email) .single()supabase/migrations/20251228065406_user_email_preferences.sql (1)
5-16: Consider adding a CHECK constraint for schema validation (optional).The JSONB column accepts any valid JSON. A CHECK constraint could enforce that only expected keys with boolean values are stored, preventing data corruption from application bugs. However, since the application handles unknown keys gracefully by defaulting to
true, this is optional.docs/BENTO_EMAIL_PREFERENCES_SETUP.md (1)
44-46: Add language specifier to code blocks (optional linting fix).The static analysis tool flagged these fenced code blocks as missing language specifiers. While the content is plain text filter instructions, adding a specifier like
textorplaintextwould satisfy the linter.🔎 Example fix for one block
-``` +```text Tag does NOT contain: usage_limit_disabled</details> This applies to all similar blocks at lines 44, 53, 62, 71, 80, 89, 98, 107, and 116. </blockquote></details> <details> <summary>src/pages/settings/account/Notifications.vue (2)</summary><blockquote> `82-103`: **Consider adding user feedback on error.** The `toggleEmailPref` function silently fails if the update errors. Unlike `submitNotif` and `submitDoi`, there's no visible feedback to the user when a preference update fails. The toggle would appear to have worked but the change wasn't persisted. <details> <summary>🔎 Proposed fix with error toast</summary> ```diff async function toggleEmailPref(key: EmailPreferenceKey) { if (!main.user?.id) return isLoading.value = true const currentPrefs = emailPrefs.value const newValue = !(currentPrefs[key] ?? true) const updatedPrefs = { ...currentPrefs, [key]: newValue } // email_preferences is a JSONB column added in migration 20251228064121 const { data, error } = await supabase .from('users') .update({ email_preferences: updatedPrefs, } as any) .eq('id', main.user.id) .select() .single() - if (!error && data) + if (!error && data) { main.user = data + } + else if (error) { + // Revert UI expectation - toast could be added here + console.error('Failed to update email preference:', error) + } isLoading.value = false }
18-31: Type duplication across frontend and backend.The
EmailPreferencesinterface is duplicated in multiple locations (this file,cron_email.ts,user_preferences.ts,org_email_notifications.ts). Consider extracting to a shared location or generating from the database schema to ensure consistency as preferences evolve.supabase/functions/_backend/utils/user_preferences.ts (1)
24-36: Consider exportingEmailPreferenceKeyandEmailPreferencestypes.These types are currently private to this module but are duplicated in
cron_email.tsandorg_email_notifications.ts. Exporting them would establish this file as the canonical source for email preference types in the backend.🔎 Proposed fix
-type EmailPreferenceKey = keyof typeof EMAIL_PREF_DISABLED_TAGS +export type EmailPreferenceKey = keyof typeof EMAIL_PREF_DISABLED_TAGS -interface EmailPreferences { +export interface EmailPreferences { usage_limit?: boolean credit_usage?: boolean onboarding?: boolean weekly_stats?: boolean monthly_stats?: boolean deploy_stats_24h?: boolean bundle_created?: boolean bundle_deployed?: boolean device_error?: boolean }Also applies to: 111-112
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
docs/BENTO_EMAIL_PREFERENCES_SETUP.mdmessages/en.jsonsrc/auto-imports.d.tssrc/components.d.tssrc/pages/settings/account/Notifications.vuesrc/typed-router.d.tssrc/types/supabase.types.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/utils/user_preferences.tssupabase/migrations/20251228065406_user_email_preferences.sqlsupabase/tests/40_test_email_preferences.sqltests/email-preferences.test.ts
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
tests/email-preferences.test.tssrc/components.d.tssupabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssrc/auto-imports.d.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssrc/typed-router.d.tssupabase/functions/_backend/triggers/cron_email.tssrc/pages/settings/account/Notifications.vuesupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
tests/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Backend tests should be located in the
tests/directory and use Vitest test runner
Files:
tests/email-preferences.test.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
tests/email-preferences.test.tssrc/components.d.tssupabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssrc/auto-imports.d.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssrc/typed-router.d.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
tests/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend tests must use helpers from
tests/test-utils.tsincludinggetEndpointUrl(path)for correct worker routing andUSE_CLOUDFLARE_WORKERS=truefor CF Workers testing
Files:
tests/email-preferences.test.ts
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
tests/email-preferences.test.tssupabase/tests/40_test_email_preferences.sqlsupabase/migrations/20251228065406_user_email_preferences.sql
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/components.d.tssrc/auto-imports.d.tssrc/typed-router.d.tssrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/components.d.tssrc/auto-imports.d.tssrc/typed-router.d.tssrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/components.d.tssrc/auto-imports.d.tssrc/typed-router.d.tssrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/components.d.tssrc/auto-imports.d.tssrc/typed-router.d.tssrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Database migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251228065406_user_email_preferences.sql
supabase/migrations/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/*.sql: When creating schema changes, usesupabase migration new <feature_slug>to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if needed
Files:
supabase/migrations/20251228065406_user_email_preferences.sql
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.ts
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue componentsUse DaisyUI (
d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Files:
src/pages/settings/account/Notifications.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/account/Notifications.vue
src/**/*.{vue,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Files:
src/pages/settings/account/Notifications.vue
src/**/*.{css,scss,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the Capgo design palette from
src/styles/style.css(e.g.,--color-primary-500: #515271,--color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii
Files:
src/pages/settings/account/Notifications.vue
🧠 Learnings (24)
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to **/{migrations,tests,__tests__}/**/*.{sql,ts,js} : Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Applied to files:
tests/email-preferences.test.tssupabase/tests/40_test_email_preferences.sql
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
tests/email-preferences.test.tssupabase/tests/40_test_email_preferences.sqlsupabase/migrations/20251228065406_user_email_preferences.sqlsupabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to playwright/e2e/**/*.{ts,js} : Cover customer-facing flows with Playwright MCP suite; add scenarios under `playwright/e2e` and run them locally with `bun run test:front` before shipping UI changes
Applied to files:
tests/email-preferences.test.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to src/**/*.{vue,css,scss} : The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Applied to files:
src/components.d.tssrc/typed-router.d.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to src/**/*.{vue,ts,tsx,js} : Konsta components are reserved for the safe area helpers; avoid importing `konsta` anywhere else in the app
Applied to files:
src/components.d.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to supabase/migrations/*.sql : A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/migrations/20251228065406_user_email_preferences.sql
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251228065406_user_email_preferences.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/utils/plans.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/triggers/cron_email.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/auto-imports.d.tssrc/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/stores/**/*.{ts,tsx} : Use Pinia stores for state management
Applied to files:
src/auto-imports.d.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/pages/**/*.vue : Frontend file-based routing uses `src/pages/` directory structure; routes auto-generate with `unplugin-vue-router` and types are available in `src/typed-router.d.ts`
Applied to files:
src/typed-router.d.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/pages/**/*.vue : Use file-based routing with unplugin-vue-router for frontend pages
Applied to files:
src/typed-router.d.ts
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/layouts/**/*.vue : Page layout components should be organized in `src/layouts/` directory
Applied to files:
src/typed-router.d.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.vue : Use `useRoute()` from `vue-router` to access route parameters and `useRouter()` for programmatic navigation in Vue components
Applied to files:
src/typed-router.d.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.vue : Use Vue 3 `<script setup>` syntax exclusively for all Vue component scripts
Applied to files:
src/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-12-25T11:22:19.594Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:19.594Z
Learning: In the 2FA enforcement implementation for supabase/migrations: When an org has enforcing_2fa=true, all users including super_admins must have 2FA enabled before accessing any org functions (including check_org_members_2fa_enabled); this is intentional behavior to ensure consistent security enforcement without exceptions for admins.
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
📚 Learning: 2025-10-14T17:30:06.380Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1222
File: supabase/migrations/20251014135440_add_cron_sync_sub.sql:78-86
Timestamp: 2025-10-14T17:30:06.380Z
Learning: The cron_sync_sub function will NOT be deployed on Cloudflare, so messages queued for 'cron_sync_sub' should not include function_type: 'cloudflare'.
Applied to files:
supabase/functions/_backend/utils/supabase.types.tssrc/types/supabase.types.ts
🧬 Code graph analysis (8)
supabase/functions/_backend/triggers/on_deploy_history_create.ts (2)
supabase/functions/_backend/utils/utils.ts (1)
backgroundTask(145-158)supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendEmailToOrgMembers(108-157)
src/auto-imports.d.ts (1)
src/stores/webhooks.ts (1)
useWebhooksStore(68-495)
supabase/functions/_backend/plugins/stats.ts (1)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendNotifToOrgMembers(172-228)
supabase/functions/_backend/utils/user_preferences.ts (1)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
EmailPreferenceKey(10-19)
supabase/functions/_backend/triggers/on_version_create.ts (2)
supabase/functions/_backend/utils/utils.ts (1)
backgroundTask(145-158)supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendEmailToOrgMembers(108-157)
supabase/functions/_backend/triggers/cron_email.ts (3)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
EmailPreferenceKey(10-19)supabase/functions/_backend/utils/supabase.ts (1)
supabaseAdmin(74-83)supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)
supabase/functions/_backend/utils/plans.ts (1)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendNotifToOrgMembers(172-228)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
Json(1-7)
🪛 GitHub Actions: Run tests
supabase/functions/_backend/utils/org_email_notifications.ts
[error] 10-10: ESLint: '=' should be placed at the beginning of the line (style/operator-linebreak)
[error] 33-33: ESLint: More than 1 blank line not allowed (style/no-multiple-empty-lines)
🪛 markdownlint-cli2 (0.18.1)
docs/BENTO_EMAIL_PREFERENCES_SETUP.md
44-44: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
62-62: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
71-71: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
80-80: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
89-89: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
98-98: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
107-107: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
116-116: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (19)
supabase/functions/_backend/utils/plans.ts (1)
7-7: Org notification calls now correctly respect per-user preferencesThe switch to
sendNotifToOrgMembersand use of preference keys'usage_limit'and'onboarding'is consistent with the new email preference model; cron strings and payloads also look reasonable for upgrade/usage/onboarding nudges.Please just double‑check that these string literals stay in sync with the
EmailPreferenceKeyunion inorg_email_notifications.tsso type safety isn’t lost on future refactors.Also applies to: 323-325, 348-350, 361-363, 374-375, 440-441
supabase/functions/_backend/triggers/on_version_create.ts (1)
7-7: Bundle-created trigger correctly delegates to preference-aware org email helperUsing
sendEmailToOrgMemberswith the'bundle_created'preference key and wrapping it inbackgroundTaskis a clean way to make this trigger respect per-user email preferences without slowing down the webhook.Just ensure
'bundle_created'remains part of theEmailPreferenceKeyunion so any future refactors stay type-safe.Also applies to: 44-48
supabase/functions/_backend/triggers/on_deploy_history_create.ts (1)
7-7: Deploy-history trigger now reuses org email helper with the right preference keyThe new
sendEmailToOrgMemberscall for'bundle:deployed'(key'bundle_deployed') is wired in at the right place—after confirming the channel is public and resolving the version—and is wrapped inbackgroundTaskso it behaves like existing LogSnag tracking.Again, double-check that
'bundle_deployed'matches theEmailPreferenceKeyliteral set used inorg_email_notifications.tsand the SQL tests.Also applies to: 62-67
supabase/tests/40_test_email_preferences.sql (1)
1-290: Comprehensive SQL coverage forusers.email_preferencesbehaviorThis test file does a solid job validating the new column end‑to‑end at the database level: existence, JSONB type, NOT NULL, default keys/values, GIN index, single/multi‑key updates, full replacement, and forward‑compatible extra keys. Wrapping everything in a transaction and rolling back keeps the tests non‑destructive.
Test 11’s invalid JSON block effectively just confirms Postgres’ JSONB cast behavior (rather than a column-specific constraint), but that’s harmless and still documents the expectation.
supabase/functions/_backend/triggers/credit_usage_alerts.ts (1)
6-6: Credit-usage alerts now correctly route through per-member, preference-aware notificationsRouting credit usage alerts via
sendNotifToOrgMemberswith preference key'credit_usage'and a monthly cron ('0 0 1 * *') cleanly aligns this trigger with the new org email notification infrastructure. The uniqId composition (alert_cycle:threshold) preserves de‑duplication semantics per org when combined withowner_orgin the notifications table.As with other call sites, please ensure
'credit_usage'remains in theEmailPreferenceKeyunion used byorg_email_notifications.ts.Also applies to: 57-65
src/typed-router.d.ts (1)
376-382: LGTM!The new Webhooks route entries in both
RouteNamedMapand_RouteFileInfoMapfollow the established auto-generated patterns and are consistent with the existing route definitions.Also applies to: 703-708
supabase/functions/_backend/triggers/cron_email.ts (1)
170-176: LGTM!The preference check is correctly placed at the start of the handler, returning early with a 200 status when the preference is disabled. The structured logging follows the codebase conventions.
supabase/migrations/20251228065406_user_email_preferences.sql (1)
5-16: LGTM!The migration correctly adds the
email_preferencesJSONB column with sensible defaults (all enabled for backwards compatibility), usesIF NOT EXISTSfor idempotency, and applies aNOT NULLconstraint. The GIN index supports efficient filtering on preference keys.docs/BENTO_EMAIL_PREFERENCES_SETUP.md (1)
1-160: Well-structured documentation.The Bento configuration guide is comprehensive, with clear mapping between preference keys and tags, step-by-step automation setup, and practical troubleshooting. The technical notes accurately reference the implementation functions (
syncUserPreferenceTags,addTagBento).supabase/functions/_backend/plugins/stats.ts (1)
11-11: LGTM!The migration from
sendNotifOrgtosendNotifToOrgMemberscorrectly integrates the per-user email preference system. The'device_error'preference key is appropriate for update failure notifications, and the event name'user:update_fail'aligns with the Bento configuration documented indocs/BENTO_EMAIL_PREFERENCES_SETUP.md.Also applies to: 120-125
src/pages/settings/account/Notifications.vue (1)
116-222: Well-organized UI with clear preference groupings.The template effectively groups related notification preferences into logical sections (General, Usage Alerts, Activity, Statistics, Issues, Onboarding). The Toggle components are consistently wired to the reactive helpers, and the Tailwind/DaisyUI classes follow the codebase's dark mode patterns.
src/components.d.ts (1)
66-67: LGTM!The new
WebhookDeliveryLogandWebhookFormcomponent declarations are auto-generated by unplugin-vue-components and follow the established patterns.Also applies to: 125-126
supabase/functions/_backend/utils/user_preferences.ts (2)
10-36: Well-designed single source of truth for preference tags.The
EMAIL_PREF_DISABLED_TAGSconst object with derivedEmailPreferenceKeytype provides type-safe tag name management. The inverted tag logic (adding_disabledtags when preferences are OFF) correctly implements the Bento exclusion pattern documented inBENTO_EMAIL_PREFERENCES_SETUP.md.
66-80: LGTM!The granular preference loop correctly implements the inverted tag logic: enabled preferences queue the disabled tag for removal, while disabled preferences queue it for addition. The default-to-true behavior ensures backwards compatibility for existing users.
src/auto-imports.d.ts (1)
10-10: Verify webhook additions are intentional scope for this PR.This PR is titled "implement granular per-user email notification preferences" but includes webhook-related global type declarations. While the AI summary mentions webhook integration, these additions appear unrelated to the core email preferences feature. Consider splitting webhook functionality into a separate PR to maintain focused, reviewable changes.
Note: This file is auto-generated, so the underlying source (likely
src/stores/webhooks.ts) would need to be reviewed separately if webhooks are intentional scope.Also applies to: 293-293, 334-336
supabase/functions/_backend/utils/org_email_notifications.ts (3)
6-31: LGTM - Type definitions align with PR requirements.The
EmailPreferenceKeyunion type andEmailPreferencesinterface correctly define the 9 preference keys mentioned in the PR summary. Using optional booleans allows proper default-enabled behavior when preferences are missing.
108-157: LGTM - Clean implementation with proper error handling.The function correctly iterates through eligible recipients, tracks Bento events, logs failures, and returns a success count. However, it depends on
getEligibleOrgMemberEmailswhich currently has a critical bug (see previous comment).
10-10: Fix ESLint violations to pass pipeline.Two style issues are blocking the pipeline:
- Line 10: The
|operator should be at the beginning of the next line, not the end of the current line- Line 33: Remove one blank line (only 1 blank line allowed)
As per coding guidelines, backend ESLint must pass before commit.
🔎 Fixes for ESLint violations
export type EmailPreferenceKey = - | 'usage_limit' + 'usage_limit' | 'credit_usage' | 'onboarding'device_error?: boolean } - /**Also applies to: 33-33
⛔ Skipped due to learnings
Learnt from: CR Repo: Cap-go/capgo PR: 0 File: .github/copilot-instructions.md:0-0 Timestamp: 2025-12-23T02:53:12.055Z Learning: Applies to supabase/functions/**/*.{ts,js} : Backend ESLint must pass before commit; run `bun lint:backend` for backend filesLearnt from: CR Repo: Cap-go/capgo PR: 0 File: .github/copilot-instructions.md:0-0 Timestamp: 2025-12-23T02:53:12.055Z Learning: Applies to src/**/*.{vue,ts,js} : Frontend ESLint must pass before commit; run `bun lint:fix` to auto-fix issues in frontend filesmessages/en.json (1)
872-895: LGTM - Well-organized localization for notification preferences.The new translation keys are clearly organized into logical sections (General, Usage Alerts, Activity, Statistics, Issues & Errors, Onboarding) with descriptive labels and help text. Key naming follows consistent patterns and aligns with the backend
EmailPreferenceKeytypes.
e4dad9c to
67501e8
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (3)
supabase/functions/_backend/utils/org_email_notifications.ts (2)
100-133: Past review comment about missingemail_preferencesappears outdatedA previous review (lines 100-105 in past comments) flagged that
email_preferencesis never loaded, causing preference filters to fail. However, the current code does fetchemail_preferencesin a separate query at lines 100-105:const { data: users } = await supabaseAdmin(c) .from('users') .select('id, email, email_preferences') .in('id', userIds)The preferences are then mapped (lines 107-113) and used for filtering (lines 117-130). The filtering logic appears to work correctly.
Minor concern: There's no error handling if the second query fails. Consider logging if
usersis null/undefined or the query returns an error.
241-296: Management email bypasses user preference checkAt line 262,
sendNotifOrgis called first, which sends the notification toorg.management_emailimmediately (see code snippet in relevant_code_snippets, line 90). This happens before the code checks user-level preferences at line 270 viagetAllEligibleEmails.Impact: If
management_emailbelongs to a user who has disabled this notification preference in their personal settings, they will still receive the email becausesendNotifOrgdoesn't check user-level preferences—it only checks org-level throttling and sends directly tomanagement_email.Suggested fix: Before calling
sendNotifOrg, check ifmanagement_emailcorresponds to a user with this preference disabled. Only proceed withsendNotifOrgif the preference is enabled or ifmanagement_emaildoesn't match any user account.This aligns with the PR's goal of respecting per-user email preferences for all notification types.
supabase/functions/_backend/utils/supabase.types.ts (1)
1265-1321:email_preferencesis still missing fromorgs/userstypes – regenerate this file from the latest schemaThe migrations for this PR introduce an
email_preferencesJSONB column onpublic.users(and related org-level email preference behavior), but theorgsanduserstable types here still don’t expose anemail_preferencesfield inRow/Insert/Update. That forces backend code to use unsafe casts when working with notification preferences and undermines type safety. Please regeneratesupabase.functions/_backend/utils/supabase.types.tsfrom the current database schema sousers.email_preferences(and any org-level preference fields, if present in the DB) are correctly typed asJson | null(or your sharedJsonalias) across Row/Insert/Update.Also applies to: 1793-1834
🧹 Nitpick comments (7)
messages/ru.json (1)
1223-1264: New notification/org-notification strings look correct; consider aligning “bundle” terminologyThe new Russian strings correctly cover all notification categories (usage, credits, activity, stats, issues, onboarding, org-level variants) and the descriptions are clear and accurate. JSON structure and interpolation look safe.
For consistency with the rest of
messages/ru.json, you may want to replaceбандл/бандлыwith the term already used for bundles (e.g.пакет/пакеты) in:
notifications-bundle-creatednotifications-bundle-created-descnotifications-bundle-deployednotifications-bundle-deployed-desc- Org-level
*-bundle-*descriptionsThis keeps UI wording uniform across the app.
messages/it.json (1)
1223-1264: Translations cover all notification categories; minor noun consistency for “bundle”The added Italian strings correctly mirror the notification and org-notification surface (usage limit/credit, activity, deploy stats 24h, weekly/monthly stats, device errors, onboarding, channel self‑rejected). JSON is valid and there are no placeholder issues.
Small optional polish: elsewhere in this file “bundle” is typically translated as “pacchetto/pacchetti`. Here you use “bundle” in:
notifications-bundle-creatednotifications-bundle-created-descnotifications-bundle-deployednotifications-bundle-deployed-desc- Related org-level
*-bundle-*descriptionsStandardizing on “pacchetto” would make the UI slightly more consistent.
messages/vi.json (1)
1223-1264: Vietnamese texts are clear; optionally align “bundle” vs “gói”The new Vietnamese entries correctly describe each notification type and the org-level variants, and the JSON is structurally sound.
For consistency with earlier keys (where “bundle” is usually translated as “gói”), you might want to replace the English word “bundle” in:
notifications-bundle-creatednotifications-bundle-created-descnotifications-bundle-deployednotifications-bundle-deployed-desc- Corresponding org-level
*-bundle-*descriptionsThis is purely stylistic; current strings are understandable.
messages/fr.json (1)
1231-1272: French notification strings look good; consider standardizing the “bundle” termThe added French strings for user and org notifications (usage, credits, activity, deploy stats, weekly/monthly stats, device errors, onboarding, channel self‑rejected) are accurate and idiomatic, and
webhooks-descriptionreads well. Structure and keys line up with other locales.The file already mixes several French renderings of “bundle” (
lot,paquet, etc.); these new keys use “bundles” (English). If you want a more polished UX, you could standardize on a single term (e.g. always “bundle(s)” or always “lot(s)/paquet(s)”) across:
notifications-bundle-created*notifications-bundle-deployed*- The corresponding org-level
*-bundle-*descriptions.Not required for correctness, just consistency.
src/pages/settings/organization/Notifications.vue (1)
36-40: Consider improving type safety for email_preferencesThe code uses
as anytype assertions when accessingemail_preferences. While this works, consider defining a proper type insupabase.types.tsfor the JSONB column to improve type safety.For example, you could extend the generated types:
type OrgWithPreferences = Database['public']['Tables']['orgs']['Row'] & { email_preferences: EmailPreferences }This is optional and can be deferred, but would eliminate the need for runtime type assertions.
Also applies to: 63-70, 78-80
tests/email-preferences.test.ts (1)
77-78: Consider using proper types instead ofas anyThroughout the test file,
email_preferencesis cast usingas any. While acceptable in tests, consider importing or defining proper types to catch type mismatches during development.This is a nice-to-have improvement and can be addressed alongside the interface duplication fix.
Also applies to: 107-108, 126-127, 148-149, 166-167, 188-189, 234-235, 280-281, 320-321, 361-362, 410-411, 432-433, 477-478, 492-493
src/pages/settings/account/Notifications.vue (1)
34-38: Type safety concern: Type assertion bypasses checkingThe type assertion
(main.user as any)?.email_preferencesbypasses TypeScript's type checking for theemail_preferencescolumn. Sincesrc/types/supabase.types.tstypes this column asJson, direct access loses type safety.Consider adding a runtime validation or using a type guard to ensure the shape matches
EmailPreferencesbefore accessing preference keys.🔎 Suggested type guard pattern
+function isEmailPreferences(value: unknown): value is EmailPreferences { + return typeof value === 'object' && value !== null +} + const emailPrefs = computed<EmailPreferences>(() => { - const prefs = (main.user as any)?.email_preferences as EmailPreferences | null | undefined + const raw = main.user?.email_preferences + const prefs = raw && isEmailPreferences(raw) ? raw : null return prefs ?? {} })
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
docs/BENTO_EMAIL_PREFERENCES_SETUP.mdmessages/de.jsonmessages/en.jsonmessages/es.jsonmessages/fr.jsonmessages/hi.jsonmessages/id.jsonmessages/it.jsonmessages/ja.jsonmessages/ko.jsonmessages/pl.jsonmessages/pt-br.jsonmessages/ru.jsonmessages/tr.jsonmessages/vi.jsonmessages/zh-cn.jsonsrc/pages/settings/account/Notifications.vuesrc/pages/settings/organization/Notifications.vuesrc/types/supabase.types.tssupabase/functions/_backend/plugins/channel_self.tssupabase/functions/_backend/plugins/stats.tssupabase/functions/_backend/triggers/credit_usage_alerts.tssupabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/triggers/on_deploy_history_create.tssupabase/functions/_backend/triggers/on_version_create.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/supabase.types.tssupabase/functions/_backend/utils/user_preferences.tssupabase/migrations/20251228065406_user_email_preferences.sqlsupabase/tests/40_test_email_preferences.sqltests/email-preferences.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
- supabase/functions/_backend/plugins/stats.ts
- supabase/functions/_backend/triggers/on_version_create.ts
- supabase/tests/40_test_email_preferences.sql
- supabase/functions/_backend/triggers/credit_usage_alerts.ts
- supabase/functions/_backend/triggers/on_deploy_history_create.ts
🧰 Additional context used
📓 Path-based instructions (19)
supabase/migrations/**/*.sql
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Database migrations must be created with
supabase migration new <feature_slug>and never modify previously committed migrations
Files:
supabase/migrations/20251228065406_user_email_preferences.sql
**/{migrations,tests,__tests__}/**/*.{sql,ts,js}
📄 CodeRabbit inference engine (AGENTS.md)
Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Files:
supabase/migrations/20251228065406_user_email_preferences.sqltests/email-preferences.test.ts
supabase/migrations/*.sql
📄 CodeRabbit inference engine (AGENTS.md)
supabase/migrations/*.sql: When creating schema changes, usesupabase migration new <feature_slug>to create a single migration file and keep editing that file until the feature ships; never edit previously committed migrations
A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Do not create new cron jobs; instead update theprocess_all_cron_tasksfunction in a new migration file to add your job if needed
Files:
supabase/migrations/20251228065406_user_email_preferences.sql
**/*.{ts,tsx,js,jsx,vue}
📄 CodeRabbit inference engine (CLAUDE.md)
Use single quotes and no semicolons per @antfu/eslint-config
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssrc/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vuesupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tstests/email-preferences.test.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**
📄 CodeRabbit inference engine (CLAUDE.md)
Backend logic should be organized in
supabase/functions/_backend/with subdirectories for plugins, private endpoints, public endpoints, triggers, and utilities
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tssupabase/functions/_backend/utils/supabase.types.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use TypeScript strict mode with path aliases mapping
~/tosrc/
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tstests/email-preferences.test.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Supabase Edge Functions use Deno runtime
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/_backend/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
supabase/functions/_backend/**/*.{ts,js}: Backend code must be placed insupabase/functions/_backend/as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
UsecreateHonofromutils/hono.tsfor all Hono framework application initialization and routing
All database operations must usegetPgClient()orgetDrizzleClient()fromutils/pg.tsfor PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must acceptContext<MiddlewareKeyVariables>and usec.get('requestId'),c.get('apikey'), andc.get('auth')for request context
Use structured logging withcloudlog({ requestId: c.get('requestId'), message: '...' })for all backend logging
UsemiddlewareAPISecretfor internal API endpoints andmiddlewareKeyfor external API keys; validate againstowner_orgin theapikeystable
Checkc.get('auth')?.authTypeto determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns withschemafrompostgress_schema.tsfor all database operations; usealiasV2()for self-joins or multiple table references
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tssupabase/functions/_backend/utils/supabase.types.ts
supabase/functions/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend ESLint must pass before commit; run
bun lint:backendfor backend files
Files:
supabase/functions/_backend/triggers/cron_email.tssupabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/user_preferences.tssupabase/functions/_backend/utils/org_email_notifications.tssupabase/functions/_backend/plugins/channel_self.tssupabase/functions/_backend/utils/supabase.types.ts
src/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
src/**/*.vue: Use Vue 3 with Composition API and<script setup>syntax for frontend components
Style components using TailwindCSS with DaisyUI components
src/**/*.vue: Use Vue 3<script setup>syntax exclusively for all Vue component scripts
Use Tailwind utility classes for layout and spacing in Vue components
Use DaisyUI components (d-btn,d-input,d-card) for interactive elements in Vue components
Use Konsta components ONLY for safe area helpers (top/bottom insets) in Vue components; avoid other uses
UseuseRoute()fromvue-routerto access route parameters anduseRouter()for programmatic navigation in Vue componentsUse DaisyUI (
d-prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vue
src/pages/**/*.vue
📄 CodeRabbit inference engine (CLAUDE.md)
Use file-based routing with unplugin-vue-router for frontend pages
Frontend file-based routing uses
src/pages/directory structure; routes auto-generate withunplugin-vue-routerand types are available insrc/typed-router.d.ts
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vue
{capacitor.config.{ts,js},src/**/*.{ts,tsx,vue}}
📄 CodeRabbit inference engine (CLAUDE.md)
Mobile apps should use Capacitor with app ID
ee.forgr.capacitor_gofor native mobile functionality
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use
~/alias for imports fromsrc/directory in frontend TypeScript and Vue components
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{vue,ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Frontend ESLint must pass before commit; run
bun lint:fixto auto-fix issues in frontend files
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{vue,css,scss}
📄 CodeRabbit inference engine (AGENTS.md)
The web client is built with Vue.js and Tailwind CSS; lean on utility classes and composition-friendly patterns rather than bespoke CSS
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vue
src/**/*.{vue,ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
Konsta components are reserved for the safe area helpers; avoid importing
konstaanywhere else in the app
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vuesrc/types/supabase.types.ts
src/**/*.{css,scss,vue}
📄 CodeRabbit inference engine (AGENTS.md)
Mirror the Capgo design palette from
src/styles/style.css(e.g.,--color-primary-500: #515271,--color-azure-500: #119eff) when introducing new UI, using deep slate bases with the Extract azure highlight and soft radii
Files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vue
tests/**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Backend tests should be located in the
tests/directory and use Vitest test runner
Files:
tests/email-preferences.test.ts
tests/**/*.{ts,js}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Backend tests must use helpers from
tests/test-utils.tsincludinggetEndpointUrl(path)for correct worker routing andUSE_CLOUDFLARE_WORKERS=truefor CF Workers testing
Files:
tests/email-preferences.test.ts
🧠 Learnings (19)
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to supabase/seed.sql : Update `supabase/seed.sql` to back new or evolved tests; keep fixtures focused on current behavior while leaving committed migrations unchanged
Applied to files:
supabase/migrations/20251228065406_user_email_preferences.sqltests/email-preferences.test.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to supabase/migrations/*.sql : A migration that introduces a new table may include seed inserts for that table, treating seeding as part of the current feature and not modifying previously committed migrations
Applied to files:
supabase/migrations/20251228065406_user_email_preferences.sqlsupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-25T11:22:13.039Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:85-96
Timestamp: 2025-12-25T11:22:13.039Z
Learning: In SQL migrations under the repository (e.g., supabase/migrations), enforce that when an org has enforcing_2fa=true, all users (including super_admins) must have 2FA enabled before accessing any org functions, including check_org_members_2fa_enabled. Do not grant admin exceptions to 2FA requirements. This ensures consistent security enforcement across all org-related operations; implement this rule within relevant migrations and associated stored procedures/tests.
Applied to files:
supabase/migrations/20251228065406_user_email_preferences.sql
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All Hono endpoint handlers must accept `Context<MiddlewareKeyVariables>` and use `c.get('requestId')`, `c.get('apikey')`, and `c.get('auth')` for request context
Applied to files:
supabase/functions/_backend/triggers/cron_email.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `middlewareAPISecret` for internal API endpoints and `middlewareKey` for external API keys; validate against `owner_org` in the `apikeys` table
Applied to files:
supabase/functions/_backend/utils/plans.tssupabase/functions/_backend/utils/org_email_notifications.tssrc/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use `createHono` from `utils/hono.ts` for all Hono framework application initialization and routing
Applied to files:
supabase/functions/_backend/utils/plans.tssupabase/functions/_backend/plugins/channel_self.ts
📚 Learning: 2025-12-23T01:19:04.593Z
Learnt from: riderx
Repo: Cap-go/capgo PR: 1297
File: src/components/dashboard/DeploymentBanner.vue:77-79
Timestamp: 2025-12-23T01:19:04.593Z
Learning: In the Cap-go codebase, ensure that app permission checks never include the role 'owner'. App-level permissions should be based on the user_min_right enum with values: read, upload, write, admin, super_admin (and NOT owner). This pattern applies across Vue components that perform permission checks; if you find a check referencing 'owner' for app-level access, replace it with the appropriate user_min_right value and keep organization-level owner handling in organization.ts.
Applied to files:
src/pages/settings/organization/Notifications.vuesrc/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-05T17:34:25.556Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-05T17:34:25.556Z
Learning: Applies to src/**/*.vue : Use Vue 3 with Composition API and `<script setup>` syntax for frontend components
Applied to files:
src/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to src/**/*.vue : Use Vue 3 `<script setup>` syntax exclusively for all Vue component scripts
Applied to files:
src/pages/settings/account/Notifications.vue
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to **/{migrations,tests,__tests__}/**/*.{sql,ts,js} : Always cover database changes with Postgres-level tests and complement them with end-to-end tests for affected user flows
Applied to files:
tests/email-preferences.test.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-27T03:51:23.575Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-27T03:51:23.575Z
Learning: Applies to playwright/e2e/**/*.{ts,js} : Cover customer-facing flows with Playwright MCP suite; add scenarios under `playwright/e2e` and run them locally with `bun run test:front` before shipping UI changes
Applied to files:
tests/email-preferences.test.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/utils/postgress_schema.ts : Schema definitions must be placed in `utils/postgress_schema.ts` using Drizzle ORM and never edited in committed migration files
Applied to files:
src/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use Drizzle ORM query patterns with `schema` from `postgress_schema.ts` for all database operations; use `aliasV2()` for self-joins or multiple table references
Applied to files:
src/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-24T14:11:10.256Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1300
File: supabase/migrations/20251224103713_2fa_enforcement.sql:409-539
Timestamp: 2025-12-24T14:11:10.256Z
Learning: In supabase/migrations for get_orgs_v6 and get_orgs_v7: The inner functions with user_id parameter (get_orgs_v6(uuid) and get_orgs_v7(uuid)) should NOT be granted to anon/authenticated roles as this allows any user to query other users' organizations; only the no-argument wrapper functions should be public as they perform authentication checks.
Applied to files:
src/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : All database operations must use `getPgClient()` or `getDrizzleClient()` from `utils/pg.ts` for PostgreSQL access during active migration to Cloudflare D1
Applied to files:
src/types/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Check `c.get('auth')?.authType` to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Applied to files:
src/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-10-14T17:30:06.380Z
Learnt from: WcaleNieWolny
Repo: Cap-go/capgo PR: 1222
File: supabase/migrations/20251014135440_add_cron_sync_sub.sql:78-86
Timestamp: 2025-10-14T17:30:06.380Z
Learning: The cron_sync_sub function will NOT be deployed on Cloudflare, so messages queued for 'cron_sync_sub' should not include function_type: 'cloudflare'.
Applied to files:
src/types/supabase.types.tssupabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to supabase/functions/_backend/**/*.{ts,js} : Use structured logging with `cloudlog({ requestId: c.get('requestId'), message: '...' })` for all backend logging
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
📚 Learning: 2025-12-23T02:53:12.055Z
Learnt from: CR
Repo: Cap-go/capgo PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-23T02:53:12.055Z
Learning: Applies to cloudflare_workers/plugin/index.ts : Plugin Worker (port 8788) routes: `/updates`, `/channel_self`, `/stats`
Applied to files:
supabase/functions/_backend/utils/supabase.types.ts
🧬 Code graph analysis (8)
supabase/functions/_backend/triggers/cron_email.ts (3)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
EmailPreferenceKey(11-21)supabase/functions/_backend/utils/supabase.ts (1)
supabaseAdmin(74-83)supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)
supabase/functions/_backend/utils/plans.ts (1)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendNotifToOrgMembers(241-296)
supabase/functions/_backend/utils/user_preferences.ts (1)
supabase/functions/_backend/utils/org_email_notifications.ts (1)
EmailPreferenceKey(11-21)
supabase/functions/_backend/utils/org_email_notifications.ts (4)
supabase/functions/_backend/utils/logging.ts (1)
cloudlog(3-15)supabase/functions/_backend/utils/utils.ts (1)
backgroundTask(145-158)supabase/functions/_backend/utils/bento.ts (1)
trackBentoEvent(49-75)supabase/functions/_backend/utils/notifications.ts (1)
sendNotifOrg(24-118)
supabase/functions/_backend/plugins/channel_self.ts (2)
supabase/functions/_backend/utils/utils.ts (1)
backgroundTask(145-158)supabase/functions/_backend/utils/org_email_notifications.ts (1)
sendNotifToOrgMembers(241-296)
tests/email-preferences.test.ts (1)
tests/test-utils.ts (7)
APP_NAME(57-57)resetAndSeedAppData(170-232)resetAppData(234-266)getSupabaseClient(324-343)USER_ID(45-45)USER_EMAIL(53-53)ORG_ID(42-42)
src/types/supabase.types.ts (1)
supabase/functions/_backend/utils/supabase.types.ts (1)
Json(1-7)
supabase/functions/_backend/utils/supabase.types.ts (1)
src/types/supabase.types.ts (1)
Json(1-7)
🪛 GitHub Actions: Run tests
src/pages/settings/organization/Notifications.vue
[error] 3-3: ESLint: 'watch' is defined but never used. (unused-imports/no-unused-imports)
🪛 markdownlint-cli2 (0.18.1)
docs/BENTO_EMAIL_PREFERENCES_SETUP.md
49-49: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
58-58: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
67-67: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
76-76: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
85-85: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
94-94: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
103-103: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
112-112: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
121-121: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (16)
messages/pl.json (1)
1223-1264: LGTM! Polish translations for notification preferences are complete and well-structured.The JSON format is valid, all translations appear properly localized to Polish, and the naming conventions are consistent. All 9 email preference keys mentioned in the PR objectives are present (usage_limit, credit_usage, onboarding, weekly_stats, monthly_stats, deploy_stats_24h, bundle_created, bundle_deployed, device_error), plus one additional preference (channel_self_rejected). This minor discrepancy with the "9 boolean keys" mentioned in the PR summary is likely due to a 10th preference being added during implementation or the summary being approximate.
messages/ko.json (1)
1223-1264: Translation additions look consistentThe new notification-related translation keys have been added correctly with proper JSON syntax. The keys align with the notification preferences feature described in the PR, covering user-level and organization-level notification settings.
messages/id.json (1)
1223-1264: Indonesian translations added correctlyThe notification preference translations have been added with proper JSON formatting and consistent key naming. Changes align with the notification preferences feature.
messages/zh-cn.json (1)
1223-1264: Simplified Chinese translations added correctlyThe notification preference translations are properly formatted and consistent with the naming conventions used in other locale files.
supabase/functions/_backend/utils/supabase.types.ts (4)
310-363: Newaudit_logs,webhook_deliveries, andwebhookstable types look consistent with expected schemasRow/Insert/Update shapes and Relationships for
audit_logs,webhook_deliveries, andwebhooksare coherent: JSON fields are typed asJson/Json | null, IDs and FKs matchorgs/users, and optionality between Row vs Insert/Update is consistent. Given this file is generated, I don’t see any schema-shape issues here.Also applies to: 1877-2002
847-882:deploy_history.install_stats_email_sent_atcolumn wiring looks correctThe new
install_stats_email_sent_atfield is correctly reflected asstring | nullinRowand optionalstring | nullinInsert/Update, matching typical timestamp columns. Just ensure the corresponding migration fordeploy_historyadds this column with a compatible type (e.g.,timestamptz) so runtime queries align with these typings.
2109-2113: Function typings for 2FA and cleanup helpers are well-formed; verify DB functions existThe added function signatures for
cleanup_old_audit_logs,cleanup_webhook_deliveries,process_deploy_install_stats_email,get_orgs_v7(both overloads),has_2fa_enabled(both overloads), andreject_access_due_to_2faare consistent with prior patterns (Args/Returns shapes, overload unions). From the types alone they look correct; just double‑check the underlying Postgres functions were created with matching signatures so Supabase RPC calls don’t fail at runtime.Also applies to: 2414-2466, 2581-2583, 2712-2713, 2777-2780
2869-2932:stats_actionenum and Constants array are aligned with new valuesAdding
"disableProdBuild"and"disableDevice"toDatabase["public"]["Enums"]["stats_action"]and mirroring them inConstants.public.Enums.stats_actionkeeps the runtime constants and compile-time union in sync. This should integrate cleanly with existing switch/lookup logic that consumesstats_action.Also applies to: 3121-3184
messages/pt-br.json (1)
1223-1264: New PT‑BR notification and org‑notification strings are consistent with the feature surfaceThe added
notifications-*andorg-notifications-*keys accurately mirror the new notification sections and organization email settings, with clear PT‑BR phrasing and no interpolation/placeholders to mismatch. This block looks ready to ship from an i18n standpoint.messages/en.json (1)
890-931: English notification and org‑notification copy aligns with the new settings UIThe new
notifications-*andorg-notifications-*keys provide clear section titles and descriptions for per-user and org-level email notification preferences, and the success/error messages read well. No placeholder variables are used here, so there’s no interpolation risk; this block looks good.supabase/functions/_backend/plugins/channel_self.ts (1)
12-12: LGTM: Notification flow updated correctlyThe migration from
sendNotifOrgtosendNotifToOrgMembersis implemented correctly across all three notification sites. The new preference-aware signature includes the'channel_self_rejected'label, enabling per-user email filtering. The fire-and-forget pattern viabackgroundTask()is appropriate for these notification calls.Also applies to: 118-129, 142-153, 445-456
src/types/supabase.types.ts (1)
1279-1279: Note: Auto-generated types with JSONB as JsonThe
email_preferencescolumns in bothorgsanduserstables are typed asJson, which is the expected TypeScript representation for PostgreSQL JSONB columns in auto-generated Supabase types. This means type safety is deferred to runtime validation in consuming code.Frontend components (such as
Notifications.vue) should implement runtime type guards or validation when accessing these preferences to maintain type safety.Also applies to: 1293-1293, 1307-1307, 1811-1811, 1825-1825, 1839-1839
messages/hi.json (1)
1223-1264: LGTM: Comprehensive Hindi translations addedThe translation additions cover all notification preference keys used in the redesigned Notifications.vue UI, including both user-level (
notifications-*) and organization-level (org-notifications-*) preferences. The keys follow the existing naming convention and provide complete coverage for the new email preference features.messages/de.json (2)
1231-1231: LGTM: JSON syntax updateThe trailing comma addition is necessary for valid JSON syntax to accommodate the new keys that follow.
1232-1272: Translation keys are consistently present across all locale filesAll 39 notification-related keys in messages/de.json have matching entries in the other 14 locale files (en.json, es.json, fr.json, hi.json, id.json, it.json, ja.json, ko.json, pl.json, pt-br.json, ru.json, tr.json, vi.json, zh-cn.json). The German translations are comprehensive and properly cover all email notification preferences.
messages/es.json (1)
1231-1272: LGTM: Consistent with German translationsThe Spanish translations maintain perfect structural consistency with the German locale file. All 41 notification keys are present in the same order with appropriate Spanish translations.
The informal "tú" form used in the descriptions is consistent with the rest of the Spanish locale file.
67501e8 to
cf2e5bc
Compare
This adds the ability for users to individually control which email notifications they receive, replacing the organization-level email settings with per-user granular preferences integrated with Bento email delivery. Key changes: - Add email_preferences JSONB column to users table with 9 preference types - Create org_email_notifications.ts utility for multi-recipient email sending - Update user_preferences.ts to sync email preference disabled tags with Bento - Update 6 backend email trigger functions to respect user preferences - Redesign Notifications.vue UI with organized preference sections - Add localization strings for new preference toggles Email preference types: - usage_limit: Plan usage threshold alerts (50/70/90%) - credit_usage: Credit usage threshold alerts - onboarding: Onboarding reminder emails - weekly_stats: Weekly statistics emails - monthly_stats: Monthly creation statistics - deploy_stats_24h: 24-hour deploy install statistics - bundle_created: New bundle upload notifications - bundle_deployed: Bundle deployment notifications - device_error: Device update error notifications Preferences default to enabled for backwards compatibility. Only users with admin or super_admin org roles receive operational emails. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add comprehensive tests for the email_preferences JSONB column: SQL tests (40_test_email_preferences.sql): - Verify column exists with correct JSONB type - Verify default values contain all 9 preference keys - Test individual and bulk preference updates - Verify GIN index exists for performance - Test forward compatibility with extra keys - Verify NOT NULL constraint E2E API tests (email-preferences.test.ts): - Test database operations for email_preferences column - Test cron_email trigger respects weekly_stats preference - Test cron_email trigger respects monthly_stats preference - Test cron_email trigger respects deploy_stats_24h preference - Test multi-preference updates - Verify org member role requirements Tests include graceful skip logic when migration isn't applied yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Document the Bento setup required for the email preferences system: - List of disabled tags and their corresponding preferences - Step-by-step automation filter configuration - Verification checklist - Troubleshooting guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add the ability for organizations to control which email notifications are sent to the organization's management email address. Key changes: - Add email_preferences JSONB column to orgs table (migration) - Update org_email_notifications.ts to check org preferences - Send to management_email only if different from admin emails AND org pref enabled - Create Notifications.vue page for organization notification settings - Add localization strings for org notification settings - Update Bento documentation with org-level preference info The management email receives notifications when: 1. It's different from all admin user emails 2. The organization has the preference enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Merge user and org email_preferences migrations into single migration - Use backgroundTask() for all email sends instead of awaiting - This ensures notifications don't block the request 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add email_preferences to getOrgInfo select query (was missing org preferences) - Add email_preferences to getEligibleOrgMemberEmails select query (was missing user preferences) - Regenerate supabase.types.ts to include email_preferences column in both users and orgs tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update channel_self.ts to use sendNotifToOrgMembers instead of sendNotifOrg for the device:channel_self_set_rejected email notification - Add channel_self_rejected to EmailPreferenceKey type in org_email_notifications.ts - Add channel_self_rejected to migration default values for users and orgs tables - Add UI toggle for channel_self_rejected in both account and org Notifications.vue - Add translations for channel_self_rejected in all 15 language files This allows users and organizations to opt out of receiving weekly emails about device channel self-assignment rejections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This adds the ability for users to individually control which email notifications they receive, replacing the organization-level email settings with per-user granular preferences integrated with Bento email delivery. Key changes: - Add email_preferences JSONB column to users table with 9 preference types - Create org_email_notifications.ts utility for multi-recipient email sending - Update user_preferences.ts to sync email preference disabled tags with Bento - Update 6 backend email trigger functions to respect user preferences - Redesign Notifications.vue UI with organized preference sections - Add localization strings for new preference toggles Email preference types: - usage_limit: Plan usage threshold alerts (50/70/90%) - credit_usage: Credit usage threshold alerts - onboarding: Onboarding reminder emails - weekly_stats: Weekly statistics emails - monthly_stats: Monthly creation statistics - deploy_stats_24h: 24-hour deploy install statistics - bundle_created: New bundle upload notifications - bundle_deployed: Bundle deployment notifications - device_error: Device update error notifications Preferences default to enabled for backwards compatibility. Only users with admin or super_admin org roles receive operational emails. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add the ability for organizations to control which email notifications are sent to the organization's management email address. Key changes: - Add email_preferences JSONB column to orgs table (migration) - Update org_email_notifications.ts to check org preferences - Send to management_email only if different from admin emails AND org pref enabled - Create Notifications.vue page for organization notification settings - Add localization strings for org notification settings - Update Bento documentation with org-level preference info The management email receives notifications when: 1. It's different from all admin user emails 2. The organization has the preference enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Merge user and org email_preferences migrations into single migration - Use backgroundTask() for all email sends instead of awaiting - This ensures notifications don't block the request 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add email_preferences to getOrgInfo select query (was missing org preferences) - Add email_preferences to getEligibleOrgMemberEmails select query (was missing user preferences) - Regenerate supabase.types.ts to include email_preferences column in both users and orgs tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add language specifiers (text) to markdown code blocks - Remove unused watch import from organization/Notifications.vue - Export EmailPreferences interface from org_email_notifications.ts - Consolidate duplicate type definitions by importing shared types - Add channel_self_rejected to EMAIL_PREF_DISABLED_TAGS for Bento sync - Fix migration ID reference in cron_email.ts comment (20251228065406) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Added 42 missing notification preference translation keys to: de, es, fr, hi, id, it, ja, ko, pl, pt-br, ru, tr, vi, zh-cn Includes translations for: - User notification preferences (general, usage, activity, issues, etc.) - Organization notification preferences - channel_self_rejected notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ebfd157 to
65dcb50
Compare
|
* feat: implement granular per-user email notification preferences This adds the ability for users to individually control which email notifications they receive, replacing the organization-level email settings with per-user granular preferences integrated with Bento email delivery. Key changes: - Add email_preferences JSONB column to users table with 9 preference types - Create org_email_notifications.ts utility for multi-recipient email sending - Update user_preferences.ts to sync email preference disabled tags with Bento - Update 6 backend email trigger functions to respect user preferences - Redesign Notifications.vue UI with organized preference sections - Add localization strings for new preference toggles Email preference types: - usage_limit: Plan usage threshold alerts (50/70/90%) - credit_usage: Credit usage threshold alerts - onboarding: Onboarding reminder emails - weekly_stats: Weekly statistics emails - monthly_stats: Monthly creation statistics - deploy_stats_24h: 24-hour deploy install statistics - bundle_created: New bundle upload notifications - bundle_deployed: Bundle deployment notifications - device_error: Device update error notifications Preferences default to enabled for backwards compatibility. Only users with admin or super_admin org roles receive operational emails. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * test: add SQL and E2E tests for email preferences feature Add comprehensive tests for the email_preferences JSONB column: SQL tests (40_test_email_preferences.sql): - Verify column exists with correct JSONB type - Verify default values contain all 9 preference keys - Test individual and bulk preference updates - Verify GIN index exists for performance - Test forward compatibility with extra keys - Verify NOT NULL constraint E2E API tests (email-preferences.test.ts): - Test database operations for email_preferences column - Test cron_email trigger respects weekly_stats preference - Test cron_email trigger respects monthly_stats preference - Test cron_email trigger respects deploy_stats_24h preference - Test multi-preference updates - Verify org member role requirements Tests include graceful skip logic when migration isn't applied yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * docs: add Bento configuration guide for email preferences Document the Bento setup required for the email preferences system: - List of disabled tags and their corresponding preferences - Step-by-step automation filter configuration - Verification checklist - Troubleshooting guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * feat: add organization-level email notification preferences Add the ability for organizations to control which email notifications are sent to the organization's management email address. Key changes: - Add email_preferences JSONB column to orgs table (migration) - Update org_email_notifications.ts to check org preferences - Send to management_email only if different from admin emails AND org pref enabled - Create Notifications.vue page for organization notification settings - Add localization strings for org notification settings - Update Bento documentation with org-level preference info The management email receives notifications when: 1. It's different from all admin user emails 2. The organization has the preference enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: consolidate migrations and use background tasks for emails - Merge user and org email_preferences migrations into single migration - Use backgroundTask() for all email sends instead of awaiting - This ensures notifications don't block the request 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: add email_preferences to select queries and regenerate types - Add email_preferences to getOrgInfo select query (was missing org preferences) - Add email_preferences to getEligibleOrgMemberEmails select query (was missing user preferences) - Regenerate supabase.types.ts to include email_preferences column in both users and orgs tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: integrate channel_self_rejected email with user preference system - Update channel_self.ts to use sendNotifToOrgMembers instead of sendNotifOrg for the device:channel_self_set_rejected email notification - Add channel_self_rejected to EmailPreferenceKey type in org_email_notifications.ts - Add channel_self_rejected to migration default values for users and orgs tables - Add UI toggle for channel_self_rejected in both account and org Notifications.vue - Add translations for channel_self_rejected in all 15 language files This allows users and organizations to opt out of receiving weekly emails about device channel self-assignment rejections. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: implement granular per-user email notification preferences This adds the ability for users to individually control which email notifications they receive, replacing the organization-level email settings with per-user granular preferences integrated with Bento email delivery. Key changes: - Add email_preferences JSONB column to users table with 9 preference types - Create org_email_notifications.ts utility for multi-recipient email sending - Update user_preferences.ts to sync email preference disabled tags with Bento - Update 6 backend email trigger functions to respect user preferences - Redesign Notifications.vue UI with organized preference sections - Add localization strings for new preference toggles Email preference types: - usage_limit: Plan usage threshold alerts (50/70/90%) - credit_usage: Credit usage threshold alerts - onboarding: Onboarding reminder emails - weekly_stats: Weekly statistics emails - monthly_stats: Monthly creation statistics - deploy_stats_24h: 24-hour deploy install statistics - bundle_created: New bundle upload notifications - bundle_deployed: Bundle deployment notifications - device_error: Device update error notifications Preferences default to enabled for backwards compatibility. Only users with admin or super_admin org roles receive operational emails. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * feat: add organization-level email notification preferences Add the ability for organizations to control which email notifications are sent to the organization's management email address. Key changes: - Add email_preferences JSONB column to orgs table (migration) - Update org_email_notifications.ts to check org preferences - Send to management_email only if different from admin emails AND org pref enabled - Create Notifications.vue page for organization notification settings - Add localization strings for org notification settings - Update Bento documentation with org-level preference info The management email receives notifications when: 1. It's different from all admin user emails 2. The organization has the preference enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: consolidate migrations and use background tasks for emails - Merge user and org email_preferences migrations into single migration - Use backgroundTask() for all email sends instead of awaiting - This ensures notifications don't block the request 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * fix: add email_preferences to select queries and regenerate types - Add email_preferences to getOrgInfo select query (was missing org preferences) - Add email_preferences to getEligibleOrgMemberEmails select query (was missing user preferences) - Regenerate supabase.types.ts to include email_preferences column in both users and orgs tables 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review comments for email preferences - Add language specifiers (text) to markdown code blocks - Remove unused watch import from organization/Notifications.vue - Export EmailPreferences interface from org_email_notifications.ts - Consolidate duplicate type definitions by importing shared types - Add channel_self_rejected to EMAIL_PREF_DISABLED_TAGS for Bento sync - Fix migration ID reference in cron_email.ts comment (20251228065406) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add notification preference translations for all 14 languages Added 42 missing notification preference translation keys to: de, es, fr, hi, id, it, ja, ko, pl, pt-br, ru, tr, vi, zh-cn Includes translations for: - User notification preferences (general, usage, activity, issues, etc.) - Organization notification preferences - channel_self_rejected notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>



Summary
Implement granular per-user email notification preferences that allow users to individually control which email notifications they receive. This replaces organization-level email settings with per-user preferences integrated with Bento for email delivery filtering. Only users with admin/super_admin roles receive operational emails, and all preferences default to enabled for backwards compatibility.
Test plan
supabase db resetto apply the migration adding theemail_preferencesJSONB column with 9 boolean preference types_disabledtags being added to the user in Bentoweekly_statspreference is enabled)Screenshots
UI sections in Account Settings > Notifications:
Checklist
bun run lint:backend && bun run lint.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.