Skip to content

feat: implement granular per-user email notification preferences#1308

Merged
riderx merged 13 commits into
mainfrom
riderx/user-email-prefs
Dec 29, 2025
Merged

feat: implement granular per-user email notification preferences#1308
riderx merged 13 commits into
mainfrom
riderx/user-email-prefs

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented Dec 28, 2025

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

  1. Database Migration: Run supabase db reset to apply the migration adding the email_preferences JSONB column with 9 boolean preference types
  2. Frontend UI: Navigate to Account Settings > Notifications to verify the new preference toggles are displayed in organized sections
  3. Toggle Preferences: Click toggles to disable individual email types and verify they persist in the database
  4. Bento Tag Sync: Verify that disabled preferences result in appropriate _disabled tags being added to the user in Bento
  5. Email Sending: Verify that emails respect user preferences (e.g., weekly stats emails only send if weekly_stats preference is enabled)
  6. Multi-recipient: Verify that emails are sent to all eligible org admins with enabled preferences
  7. Role Filtering: Verify that only admin/super_admin org members receive operational emails
  8. Backwards Compatibility: Verify that existing general notification and newsletter toggles still function

Screenshots

UI sections in Account Settings > Notifications:

  • General Settings (enable_notifications, opt_for_newsletters)
  • Usage Alerts (usage_limit, credit_usage)
  • Activity Notifications (bundle_created, bundle_deployed, deploy_stats_24h)
  • Statistics (weekly_stats, monthly_stats)
  • Issues & Errors (device_error)
  • Onboarding (onboarding)

Checklist

  • My code follows the code style of this project and passes bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps how to reproduce my tests

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added granular email notification preferences allowing users to customize which notifications they receive (usage alerts, credits, activity, statistics, device errors, onboarding).
    • Added organization-level email notification management for controlling messages sent to management email addresses.
    • Restructured notification settings interface with organized categories.
  • Documentation

    • Added email preference setup documentation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 28, 2025

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 67501e8 and 65dcb50.

📒 Files selected for processing (31)
  • docs/BENTO_EMAIL_PREFERENCES_SETUP.md
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/hi.json
  • messages/id.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/pl.json
  • messages/pt-br.json
  • messages/ru.json
  • messages/tr.json
  • messages/vi.json
  • messages/zh-cn.json
  • src/pages/settings/account/Notifications.vue
  • src/pages/settings/organization/Notifications.vue
  • src/types/supabase.types.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/migrations/20251228065406_user_email_preferences.sql
  • supabase/tests/40_test_email_preferences.sql
  • tests/email-preferences.test.ts

Note

Other AI code review bot(s) detected

CodeRabbit 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.

📝 Walkthrough

Walkthrough

Introduces granular per-user and per-organization email notification preferences system. Adds email_preferences JSONB columns to users and orgs tables, updates frontend UI with preference toggles, refactors backend notification infrastructure to check preferences before sending emails, and syncs disabled preferences to Bento tags for multi-channel filtering.

Changes

Cohort / File(s) Summary
Documentation
docs/BENTO_EMAIL_PREFERENCES_SETUP.md
New setup guide detailing Bento-based email preference system: preference key-to-tag mapping, user/org-level model, automation filter configuration, and troubleshooting steps.
User Notification Preferences UI
src/pages/settings/account/Notifications.vue
Replaces single/dual-toggle layout with structured multi-section UI (General, Usage Alerts, Activity, Statistics, Issues, Onboarding). Adds EmailPreferences interface and toggleEmailPref() handler to read/write preferences to Supabase.
Organization Notification Preferences UI
src/pages/settings/organization/Notifications.vue
New component managing organization-level email preferences. Implements similar interface and toggle logic with permission checks via hasOrgPerm, updating orgs.email_preferences JSONB column.
Localization — English & 12 other locales
messages/en.json, messages/de.json, messages/es.json, messages/fr.json, messages/hi.json, messages/id.json, messages/it.json, messages/ja.json, messages/ko.json, messages/pl.json, messages/pt-br.json, messages/ru.json, messages/tr.json, messages/vi.json, messages/zh-cn.json
Adds ~40 new translation keys per locale covering notifications sections (general, usage alerts, activity, statistics, issues, onboarding) and organization notification descriptions with corresponding -desc variants.
Frontend Type Definitions
src/types/supabase.types.ts
Extends users and orgs tables with email_preferences JSONB field; adds audit_logs, webhook_deliveries, webhooks tables; introduces install_stats_email_sent_at to deploy_history; adds 2FA-related functions and get_orgs_v7 overloads.
Database Schema & Migration
supabase/migrations/20251228065406_user_email_preferences.sql, supabase/tests/40_test_email_preferences.sql
Adds email_preferences JSONB columns (both users and orgs) with default all-enabled structure, GIN indexes, and descriptive comments; includes SQL test suite covering schema, defaults, updates, and forward compatibility.
Backend Type Definitions
supabase/functions/_backend/utils/supabase.types.ts
Mirrors frontend type updates: adds email_preferences to users/orgs, new tables (audit_logs, webhook_deliveries, webhooks), 2FA functions, and get_orgs_v7 overloads.
Backend Email Preference Utilities
supabase/functions/_backend/utils/user_preferences.ts
Introduces EMAIL_PREF_DISABLED_TAGS map, EmailPreferences interface, and preference-to-tag synchronization. Extends buildPreferenceSegments() to add/remove disabled tags based on granular preference state; exports emailPreferenceDisabledTags.
Organization Email Notification Module
supabase/functions/_backend/utils/org_email_notifications.ts
New module providing sendEmailToOrgMembers() and sendNotifToOrgMembers() functions. Handles eligibility determination (admin users + conditional management email), preference checking, and background event dispatch with error handling and logging.
Notification Infrastructure Updates
supabase/functions/_backend/utils/plans.ts
Replaces sendNotifOrg() calls with sendNotifToOrgMembers() across usage threshold (50%/70%/90%), user upgrade, and onboarding flows; adds explicit notification type parameter ('usage_limit', 'onboarding').
Preference-Aware Email Triggers
supabase/functions/_backend/triggers/cron_email.ts
Adds isEmailPreferenceEnabled() check before executing weekly stats, monthly stats, and deploy 24h stats handlers; returns 200 with status message if preference disabled.
Deploy & Bundle Event Triggers
supabase/functions/_backend/triggers/on_deploy_history_create.ts, on_version_create.ts
Replaces trackBentoEvent() invocations with calls to sendEmailToOrgMembers() for bundle deployment and version creation notifications; removes Bento tracking import.
Plugin Notification Updates
supabase/functions/_backend/plugins/stats.ts, supabase/functions/_backend/plugins/channel_self.ts, supabase/functions/_backend/triggers/credit_usage_alerts.ts
Updates notification call sites to use sendNotifToOrgMembers() instead of sendNotifOrg(), adding explicit notification type parameter ('device_error', 'channel_self_rejected', 'credit_usage') and cron schedule string.
Integration Tests
tests/email-preferences.test.ts
Comprehensive end-to-end test suite validating migration, default preferences, individual/bulk updates, and cron trigger filtering based on weekly_stats, monthly_stats, and deploy_stats_24h preferences.

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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Preferences now refined,
Each notification reassigned,
Per-user, per-org, so fine,
Bento tags aligned—
No more emails you'll find! ✉️

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main feature being added: granular per-user email notification preferences. It is concise, focused, and accurately represents the primary change in the changeset.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering Summary, Test plan with detailed steps, Screenshots of UI sections, and a Checklist. All required template sections are present and substantively filled out with relevant implementation details.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +62 to +67
// 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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +188 to +203
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/types/supabase.types.ts (1)

310-363: Keep frontend Supabase types in sync with backend schema (including users.email_preferences)

This file mirrors the backend supabase.functions/_backend/utils/supabase.types.ts changes for audit logs, webhooks, 2FA helpers, and stats_action values, which is good for consistency.

The same gap applies here: public.users has no email_preferences field in Row/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 with any casts or TS errors.

Once the migration is finalized, please regenerate this type file from the database so users.email_preferences is 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, email but line 72 attempts to access email_preferences. This field is never fetched, so prefs[preferenceKey] on line 86 is always undefined, causing all users to be treated as opted-in (line 87 defaults to true). 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 to org.management_email before 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 using getEndpointUrl for cron triggers

The test suite exercises the new email_preferences column 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 the getEndpointUrl('/triggers/cron_email') helper from test-utils.ts instead of interpolating BASE_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 importing EmailPreferences interface instead of duplicating it.

This EmailPreferences interface duplicates the one defined in supabase/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 text or plaintext would 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 EmailPreferences interface 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 exporting EmailPreferenceKey and EmailPreferences types.

These types are currently private to this module but are duplicated in cron_email.ts and org_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

📥 Commits

Reviewing files that changed from the base of the PR and between 949e24c and 553cdd1.

📒 Files selected for processing (19)
  • docs/BENTO_EMAIL_PREFERENCES_SETUP.md
  • messages/en.json
  • src/auto-imports.d.ts
  • src/components.d.ts
  • src/pages/settings/account/Notifications.vue
  • src/typed-router.d.ts
  • src/types/supabase.types.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/migrations/20251228065406_user_email_preferences.sql
  • supabase/tests/40_test_email_preferences.sql
  • tests/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.ts
  • src/components.d.ts
  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • src/auto-imports.d.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • src/typed-router.d.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • src/pages/settings/account/Notifications.vue
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • src/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 ~/ to src/

Files:

  • tests/email-preferences.test.ts
  • src/components.d.ts
  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • src/auto-imports.d.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • src/typed-router.d.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • src/types/supabase.types.ts
tests/**/*.{ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Backend tests must use helpers from tests/test-utils.ts including getEndpointUrl(path) for correct worker routing and USE_CLOUDFLARE_WORKERS=true for 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.ts
  • supabase/tests/40_test_email_preferences.sql
  • supabase/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_go for native mobile functionality

Files:

  • src/components.d.ts
  • src/auto-imports.d.ts
  • src/typed-router.d.ts
  • src/pages/settings/account/Notifications.vue
  • src/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/components.d.ts
  • src/auto-imports.d.ts
  • src/typed-router.d.ts
  • src/pages/settings/account/Notifications.vue
  • src/types/supabase.types.ts
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/components.d.ts
  • src/auto-imports.d.ts
  • src/typed-router.d.ts
  • src/pages/settings/account/Notifications.vue
  • src/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 konsta anywhere else in the app

Files:

  • src/components.d.ts
  • src/auto-imports.d.ts
  • src/typed-router.d.ts
  • src/pages/settings/account/Notifications.vue
  • src/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, use supabase 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 the process_all_cron_tasks function 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.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/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:backend for backend files

Files:

  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use 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 with unplugin-vue-router and types are available in src/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.ts
  • supabase/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.ts
  • supabase/tests/40_test_email_preferences.sql
  • supabase/migrations/20251228065406_user_email_preferences.sql
  • supabase/functions/_backend/utils/supabase.types.ts
  • src/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.ts
  • src/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.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/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.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • 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/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/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.ts
  • src/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.ts
  • 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} : 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.ts
  • 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:

  • supabase/functions/_backend/utils/supabase.types.ts
  • src/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.ts
  • src/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.ts
  • src/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.ts
  • src/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 preferences

The switch to sendNotifToOrgMembers and 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 EmailPreferenceKey union in org_email_notifications.ts so 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 helper

Using sendEmailToOrgMembers with the 'bundle_created' preference key and wrapping it in backgroundTask is a clean way to make this trigger respect per-user email preferences without slowing down the webhook.

Just ensure 'bundle_created' remains part of the EmailPreferenceKey union 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 key

The new sendEmailToOrgMembers call 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 in backgroundTask so it behaves like existing LogSnag tracking.

Again, double-check that 'bundle_deployed' matches the EmailPreferenceKey literal set used in org_email_notifications.ts and the SQL tests.

Also applies to: 62-67

supabase/tests/40_test_email_preferences.sql (1)

1-290: Comprehensive SQL coverage for users.email_preferences behavior

This 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 notifications

Routing credit usage alerts via sendNotifToOrgMembers with 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 with owner_org in the notifications table.

As with other call sites, please ensure 'credit_usage' remains in the EmailPreferenceKey union used by org_email_notifications.ts.

Also applies to: 57-65

src/typed-router.d.ts (1)

376-382: LGTM!

The new Webhooks route entries in both RouteNamedMap and _RouteFileInfoMap follow 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_preferences JSONB column with sensible defaults (all enabled for backwards compatibility), uses IF NOT EXISTS for idempotency, and applies a NOT NULL constraint. 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 sendNotifOrg to sendNotifToOrgMembers correctly 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 in docs/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 WebhookDeliveryLog and WebhookForm component 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_TAGS const object with derived EmailPreferenceKey type provides type-safe tag name management. The inverted tag logic (adding _disabled tags when preferences are OFF) correctly implements the Bento exclusion pattern documented in BENTO_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 EmailPreferenceKey union type and EmailPreferences interface 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 getEligibleOrgMemberEmails which currently has a critical bug (see previous comment).


10-10: Fix ESLint violations to pass pipeline.

Two style issues are blocking the pipeline:

  1. Line 10: The | operator should be at the beginning of the next line, not the end of the current line
  2. 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 files
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,ts,js} : Frontend ESLint must pass before commit; run `bun lint:fix` to auto-fix issues in frontend files
messages/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 EmailPreferenceKey types.

Comment thread supabase/functions/_backend/utils/supabase.types.ts
@riderx riderx force-pushed the riderx/user-email-prefs branch from e4dad9c to 67501e8 Compare December 29, 2025 02:43
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (3)
supabase/functions/_backend/utils/org_email_notifications.ts (2)

100-133: Past review comment about missing email_preferences appears outdated

A previous review (lines 100-105 in past comments) flagged that email_preferences is never loaded, causing preference filters to fail. However, the current code does fetch email_preferences in 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 users is null/undefined or the query returns an error.


241-296: Management email bypasses user preference check

At line 262, sendNotifOrg is called first, which sends the notification to org.management_email immediately (see code snippet in relevant_code_snippets, line 90). This happens before the code checks user-level preferences at line 270 via getAllEligibleEmails.

Impact: If management_email belongs to a user who has disabled this notification preference in their personal settings, they will still receive the email because sendNotifOrg doesn't check user-level preferences—it only checks org-level throttling and sends directly to management_email.

Suggested fix: Before calling sendNotifOrg, check if management_email corresponds to a user with this preference disabled. Only proceed with sendNotifOrg if the preference is enabled or if management_email doesn'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_preferences is still missing from orgs/users types – regenerate this file from the latest schema

The migrations for this PR introduce an email_preferences JSONB column on public.users (and related org-level email preference behavior), but the orgs and users table types here still don’t expose an email_preferences field in Row/Insert/Update. That forces backend code to use unsafe casts when working with notification preferences and undermines type safety. Please regenerate supabase.functions/_backend/utils/supabase.types.ts from the current database schema so users.email_preferences (and any org-level preference fields, if present in the DB) are correctly typed as Json | null (or your shared Json alias) 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” terminology

The 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-created
  • notifications-bundle-created-desc
  • notifications-bundle-deployed
  • notifications-bundle-deployed-desc
  • Org-level *-bundle-* descriptions

This 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-created
  • notifications-bundle-created-desc
  • notifications-bundle-deployed
  • notifications-bundle-deployed-desc
  • Related org-level *-bundle-* descriptions

Standardizing 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-created
  • notifications-bundle-created-desc
  • notifications-bundle-deployed
  • notifications-bundle-deployed-desc
  • Corresponding org-level *-bundle-* descriptions

This is purely stylistic; current strings are understandable.

messages/fr.json (1)

1231-1272: French notification strings look good; consider standardizing the “bundle” term

The 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-description reads 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_preferences

The code uses as any type assertions when accessing email_preferences. While this works, consider defining a proper type in supabase.types.ts for 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 of as any

Throughout the test file, email_preferences is cast using as 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 checking

The type assertion (main.user as any)?.email_preferences bypasses TypeScript's type checking for the email_preferences column. Since src/types/supabase.types.ts types this column as Json, direct access loses type safety.

Consider adding a runtime validation or using a type guard to ensure the shape matches EmailPreferences before 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

📥 Commits

Reviewing files that changed from the base of the PR and between 553cdd1 and 67501e8.

📒 Files selected for processing (32)
  • docs/BENTO_EMAIL_PREFERENCES_SETUP.md
  • messages/de.json
  • messages/en.json
  • messages/es.json
  • messages/fr.json
  • messages/hi.json
  • messages/id.json
  • messages/it.json
  • messages/ja.json
  • messages/ko.json
  • messages/pl.json
  • messages/pt-br.json
  • messages/ru.json
  • messages/tr.json
  • messages/vi.json
  • messages/zh-cn.json
  • src/pages/settings/account/Notifications.vue
  • src/pages/settings/organization/Notifications.vue
  • src/types/supabase.types.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/functions/_backend/plugins/stats.ts
  • supabase/functions/_backend/triggers/credit_usage_alerts.ts
  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/triggers/on_deploy_history_create.ts
  • supabase/functions/_backend/triggers/on_version_create.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/supabase.types.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/migrations/20251228065406_user_email_preferences.sql
  • supabase/tests/40_test_email_preferences.sql
  • tests/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.sql
  • tests/email-preferences.test.ts
supabase/migrations/*.sql

📄 CodeRabbit inference engine (AGENTS.md)

supabase/migrations/*.sql: When creating schema changes, use supabase 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 the process_all_cron_tasks function 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.ts
  • supabase/functions/_backend/utils/plans.ts
  • src/pages/settings/organization/Notifications.vue
  • src/pages/settings/account/Notifications.vue
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • tests/email-preferences.test.ts
  • src/types/supabase.types.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/functions/_backend/utils/supabase.types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use TypeScript strict mode with path aliases mapping ~/ to src/

Files:

  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • tests/email-preferences.test.ts
  • src/types/supabase.types.ts
  • supabase/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.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/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 in supabase/functions/_backend/ as shared code deployed to Cloudflare Workers (API/Plugin/Files workers), Supabase Edge Functions, and other platforms
Use createHono from utils/hono.ts for all Hono framework application initialization and routing
All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1
All Hono endpoint handlers must accept Context<MiddlewareKeyVariables> and use c.get('requestId'), c.get('apikey'), and c.get('auth') for request context
Use structured logging with cloudlog({ requestId: c.get('requestId'), message: '...' }) for all backend logging
Use middlewareAPISecret for internal API endpoints and middlewareKey for external API keys; validate against owner_org in the apikeys table
Check c.get('auth')?.authType to determine authentication type ('apikey' vs 'jwt') in backend endpoints
Use Drizzle ORM query patterns with schema from postgress_schema.ts for all database operations; use aliasV2() for self-joins or multiple table references

Files:

  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/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:backend for backend files

Files:

  • supabase/functions/_backend/triggers/cron_email.ts
  • supabase/functions/_backend/utils/plans.ts
  • supabase/functions/_backend/utils/user_preferences.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • supabase/functions/_backend/plugins/channel_self.ts
  • supabase/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
Use useRoute() from vue-router to access route parameters and useRouter() for programmatic navigation in Vue components

Use DaisyUI (d- prefixed classes) for buttons, inputs, and other interactive primitives to keep behavior and spacing consistent

Files:

  • src/pages/settings/organization/Notifications.vue
  • 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 with unplugin-vue-router and types are available in src/typed-router.d.ts

Files:

  • src/pages/settings/organization/Notifications.vue
  • src/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_go for native mobile functionality

Files:

  • src/pages/settings/organization/Notifications.vue
  • src/pages/settings/account/Notifications.vue
  • src/types/supabase.types.ts
src/**/*.{ts,tsx,vue,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ~/ alias for imports from src/ directory in frontend TypeScript and Vue components

Files:

  • src/pages/settings/organization/Notifications.vue
  • src/pages/settings/account/Notifications.vue
  • src/types/supabase.types.ts
src/**/*.{vue,ts,js}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Frontend ESLint must pass before commit; run bun lint:fix to auto-fix issues in frontend files

Files:

  • src/pages/settings/organization/Notifications.vue
  • src/pages/settings/account/Notifications.vue
  • src/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.vue
  • src/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 konsta anywhere else in the app

Files:

  • src/pages/settings/organization/Notifications.vue
  • src/pages/settings/account/Notifications.vue
  • src/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.vue
  • src/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.ts including getEndpointUrl(path) for correct worker routing and USE_CLOUDFLARE_WORKERS=true for 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.sql
  • tests/email-preferences.test.ts
  • src/types/supabase.types.ts
  • supabase/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.sql
  • supabase/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.ts
  • supabase/functions/_backend/utils/org_email_notifications.ts
  • src/types/supabase.types.ts
  • 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 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.ts
  • supabase/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.vue
  • src/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.ts
  • supabase/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.ts
  • 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 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.ts
  • supabase/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.ts
  • 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 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.ts
  • supabase/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.ts
  • 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 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 consistent

The 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 correctly

The 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 correctly

The 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: New audit_logs, webhook_deliveries, and webhooks table types look consistent with expected schemas

Row/Insert/Update shapes and Relationships for audit_logs, webhook_deliveries, and webhooks are coherent: JSON fields are typed as Json/Json | null, IDs and FKs match orgs/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_at column wiring looks correct

The new install_stats_email_sent_at field is correctly reflected as string | null in Row and optional string | null in Insert/Update, matching typical timestamp columns. Just ensure the corresponding migration for deploy_history adds 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 exist

The 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), and reject_access_due_to_2fa are 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_action enum and Constants array are aligned with new values

Adding "disableProdBuild" and "disableDevice" to Database["public"]["Enums"]["stats_action"] and mirroring them in Constants.public.Enums.stats_action keeps the runtime constants and compile-time union in sync. This should integrate cleanly with existing switch/lookup logic that consumes stats_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 surface

The added notifications-* and org-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 UI

The new notifications-* and org-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 correctly

The migration from sendNotifOrg to sendNotifToOrgMembers is 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 via backgroundTask() 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 Json

The email_preferences columns in both orgs and users tables are typed as Json, 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 added

The 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 update

The 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 files

All 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 translations

The 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.

Comment thread docs/BENTO_EMAIL_PREFERENCES_SETUP.md Outdated
Comment thread src/pages/settings/account/Notifications.vue
Comment thread src/pages/settings/organization/Notifications.vue Outdated
Comment thread supabase/functions/_backend/triggers/cron_email.ts Outdated
Comment thread supabase/functions/_backend/triggers/cron_email.ts Outdated
Comment thread supabase/functions/_backend/utils/user_preferences.ts
Comment thread tests/email-preferences.test.ts Outdated
@riderx riderx force-pushed the riderx/user-email-prefs branch from 67501e8 to cf2e5bc Compare December 29, 2025 03:56
riderx and others added 13 commits December 29, 2025 04:15
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>
@riderx riderx force-pushed the riderx/user-email-prefs branch from ebfd157 to 65dcb50 Compare December 29, 2025 04:15
@sonarqubecloud
Copy link
Copy Markdown

@riderx riderx merged commit db48dae into main Dec 29, 2025
10 of 11 checks passed
@riderx riderx deleted the riderx/user-email-prefs branch December 29, 2025 04:29
Dalanir pushed a commit that referenced this pull request Jan 12, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant