Skip to content

[codex] Add 508 members newsletter sync#332

Merged
michaelmwu merged 25 commits into
mainfrom
michaelmwu/add-users-to-brevo-list
Jun 26, 2026
Merged

[codex] Add 508 members newsletter sync#332
michaelmwu merged 25 commits into
mainfrom
michaelmwu/add-users-to-brevo-list

Conversation

@michaelmwu

@michaelmwu michaelmwu commented Jun 9, 2026

Copy link
Copy Markdown
Member

Summary

  • add shared Brevo, Keila, and Migadu newsletter-sync helpers
  • keep /create-mailbox, /create-user-accounts, and agent account provisioning on direct best-effort newsletter sync for mailbox plus backup email
  • add weekly Migadu -> 508 members newsletter reconciliation that skips configured system mailboxes, CRM-blocked contacts, and provider-suppressed contacts
  • add worker job, API scheduler, and admin dashboard manual trigger for 508 members newsletter sync
  • support Brevo list lookup by BREVO_508_MEMBERS_NEWSLETTER_LIST_NAME with production override BREVO_508_MEMBERS_NEWSLETTER_LIST_ID=4
  • support Keila project contacts using contact data audiences: ["508_members"] for segmenting

Validation

  • uv run pytest tests/ -q
  • uv run pytest tests/unit/test_brevo_client.py tests/unit/test_keila_client.py tests/unit/test_newsletter_sync.py tests/unit/test_migadu_create_mailbox.py tests/unit/test_crm_create_sso_user.py tests/unit/test_shared_settings.py tests/unit/test_agent_gateway.py tests/unit/test_backend_api.py
  • ./scripts/lint.sh
  • ./scripts/mypy.sh
  • pre-commit hook: ruff, ruff format, mypy

Frontend verification note

  • bun run lint passes through ./scripts/lint.sh
  • bun run typecheck is blocked in this workspace because apps/admin_dashboard/node_modules has TypeScript 5.9.3 and is missing local typescript/@types/bun, while the lockfile expects TypeScript 6.0.3 and @types/bun
  • bun run test is blocked because local vitest is missing from node_modules

Notes

  • Production should set BREVO_508_MEMBERS_NEWSLETTER_LIST_ID=4; non-prod can omit it and rely on list-name lookup or leave Brevo disabled.
  • Keila does not expose Brevo-style contact lists in the documented API; this sync writes contact data for a 508_members audience so Keila segments can target it.

Summary by CodeRabbit

  • New Features
    • Added newsletter contact syncing via Brevo and Keila, subscribing both the 508 mailbox email and its backup email to the configured audience/list.
    • Added an admin dashboard “Sync newsletters” action with dry-run support, loading feedback, and job/queue results.
    • Added periodic automated syncing using the 508 mailbox source-of-truth, with configurable intervals and excluded mailboxes.
  • Improvements
    • Added newsletter suppression visibility in the dashboard and best-effort newsletter sync warnings (no blocking).
    • Improved privacy by redacting email addresses in errors and previews.
  • Documentation
    • Updated environment and configuration docs for dashboard-managed locking and newsletter sync setup guidance.

Copilot AI review requested due to automatic review settings June 9, 2026 17:37
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds newsletter sync configuration, shared provider clients, sync orchestration, dashboard/API scheduling, worker masking, provisioning integrations, and dashboard controls with unit coverage.

Changes

Newsletter sync end-to-end implementation

Layer / File(s) Summary
Newsletter configuration and settings
...
Adds newsletter-related env examples, runtime configuration definitions, shared settings fields and validation, and documentation of sync behavior and dashboard locking.
Brevo, Keila, and Migadu client primitives
...
Implements Brevo and Keila clients, Migadu mailbox listing, email normalization, redaction helpers, and request/error handling tests.
Newsletter sync core and suppression registry
...
Adds the newsletter sync processor, provider implementations, suppression registry, direct sync helpers, warning formatting, migration, and processor tests.
Agent and Discord provisioning integration
...
Extends agent runtime config, mailbox provisioning, Discord audit/message handling, and related tests for newsletter status propagation.
API, worker, and dashboard wiring
...
Adds dashboard-triggered sync, scheduler lifecycle management, worker job masking, People view controls, and updated static dashboard assets.

Sequence Diagram(s)

sequenceDiagram
  participant AdminDashboard
  participant BackendAPI
  participant JobQueue
  participant WorkerJobs
  participant NewsletterSyncProcessor
  AdminDashboard->>BackendAPI: POST /dashboard/api/sync/newsletters
  BackendAPI->>JobQueue: enqueue sync_508_members_newsletters_job
  BackendAPI->>BackendAPI: write newsletter.508_members_sync audit event
  JobQueue->>WorkerJobs: dispatch by job name
  WorkerJobs->>NewsletterSyncProcessor: sync_508_members_newsletters_job()
  NewsletterSyncProcessor-->>WorkerJobs: masked sync result
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90 minutes

Possibly related PRs

  • 508-dev/508-workflows#115: Overlapping changes to the Discord mailbox provisioning code paths and Migadu/CRM workflow structure.
  • 508-dev/508-workflows#207: Prior changes to the Migadu mailbox creation flow that this PR extends with newsletter subscription behavior.
  • 508-dev/508-workflows#283: Related React admin dashboard changes in the same apps/admin_dashboard/src/main.tsx area.

Poem

🐰 I hopped through configs, keys in tow,
Brevo and Keila now learn who to know.
Mailboxes whispered, backup emails too,
Scheduler ticks, and dashboards grew.
Best-effort hops with redacted glow —
a carrot-bright sync, neat and low.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding 508 members newsletter sync.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch michaelmwu/add-users-to-brevo-list

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.

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

Copy link
Copy Markdown

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.

Reviewed commit: 3c1183a9be

ℹ️ 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 thread packages/shared/src/five08/settings.py Outdated
brevo_api_key: str | None = None
brevo_api_base_url: str = "https://api.brevo.com/v3"
brevo_api_timeout_seconds: float = 20.0
brevo_newsletter_list_id: int | None = Field(default=None, ge=1)

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 Handle blank Brevo list IDs before integer validation

When .env is copied from the updated .env.example, BREVO_NEWSLETTER_LIST_ID= is present but blank; pydantic-settings does not coerce an empty string to None for an int | None, so constructing SharedSettings fails before the optional Brevo sync can simply remain disabled. The existing optional numeric Docuseal setting has a mode="before" normalizer for this case; this new field needs the same treatment (or equivalent env_ignore_empty) so local/dev deployments with the documented blank value still start.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds best-effort Brevo newsletter subscription during mailbox/account provisioning across the agent tools and Discord bot flows, with shared configuration and a shared Brevo API client.

Changes:

  • Introduces a shared BrevoClient plus settings/runtime-config wiring for BREVO_API_KEY and BREVO_NEWSLETTER_LIST_ID.
  • Subscribes both the 508 mailbox email and backup email to a Brevo list during /create-mailbox and /create-user-accounts (non-blocking, warning surfaced).
  • Extends unit tests and docs/env examples to cover the new integration.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/shared/src/five08/clients/brevo.py Adds shared Brevo API wrapper for create-or-update contact + list subscription.
packages/shared/src/five08/settings.py Exposes Brevo settings in shared BaseSettings.
packages/shared/src/five08/agent/tools.py Adds Brevo runtime config + newsletter subscription in mailbox provisioning tool results.
apps/discord_bot/src/five08/discord_bot/cogs/migadu.py Subscribes created mailbox + backup email to Brevo after Migadu provisioning; surfaces result in embeds/audit metadata.
apps/discord_bot/src/five08/discord_bot/cogs/crm.py Subscribes mailbox + backup email to Brevo during user-account provisioning; surfaces result in messages/audit metadata.
tests/unit/test_brevo_client.py Adds unit tests for BrevoClient request payload and error handling.
tests/unit/test_shared_settings.py Extends shared settings/runtime config test coverage for Brevo fields.
tests/unit/test_migadu_create_mailbox.py Asserts newsletter sync hook is invoked in mailbox command flow.
tests/unit/test_crm_create_sso_user.py Asserts newsletter sync hook is invoked and success message includes newsletter status.
tests/unit/test_agent_gateway.py Adds Brevo fake + assertions for agent tool subscription behavior and result metadata.
ENVIRONMENT.md Documents optional Brevo newsletter sync env vars and behavior.
apps/discord_bot/README.md Documents Brevo newsletter sync behavior for /create-mailbox.
.env.example Adds Brevo env var placeholders.
Comments suppressed due to low confidence (1)

apps/discord_bot/src/five08/discord_bot/cogs/crm.py:8632

  • exc.newsletter_error is interpolated into a Discord message inside backticks without sanitization/truncation. If the underlying Brevo error contains backticks/control characters or is long (e.g., includes an HTTP body), this can break formatting or exceed Discord limits. Sanitize/truncate the newsletter error before building newsletter_line.
            self._audit_command_safe(
                interaction=interaction,
                action="crm.create_user_accounts",
                result="error",
                metadata={

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/shared/src/five08/settings.py Outdated
Comment on lines +82 to +85
brevo_api_key: str | None = None
brevo_api_base_url: str = "https://api.brevo.com/v3"
brevo_api_timeout_seconds: float = 20.0
brevo_newsletter_list_id: int | None = Field(default=None, ge=1)
Comment on lines +246 to +252
logger.warning(
"Failed to add %s to Brevo newsletter list: %s",
normalized_email,
exc,
)
errors.append(f"{normalized_email}: {exc}")
return "; ".join(errors) if errors else None
Comment on lines +3855 to +3862
except (BrevoAPIError, ValueError) as exc:
logger.warning(
"Failed to add %s to Brevo newsletter list: %s",
normalized_email,
exc,
)
errors.append(f"{normalized_email}: {exc}")
return "; ".join(errors) if errors else None
assert runtime_config.brevo_api_key == "brevo-key"
assert runtime_config.brevo_newsletter_list_id == 4


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

Copy link
Copy Markdown

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.

Reviewed commit: 6780a019e4

ℹ️ 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 +227 to +228
if client is None:
return "BREVO_API_KEY is not configured."

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 Treat missing Brevo config as disabled

When BREVO_API_KEY is intentionally unset, the documented behavior is that newsletter sync is optional, but this return value is propagated as newsletter_error, so successful /create-mailbox and /create-user-accounts flows display “Brevo subscription failed: BREVO_API_KEY is not configured” and audit newsletter_subscribed=false. In deployments that do not configure Brevo, every mailbox/account creation now looks partially failed even though sync should simply be skipped unless the key is configured.

Useful? React with 👍 / 👎.

Copilot AI review requested due to automatic review settings June 10, 2026 06:01
@michaelmwu michaelmwu changed the title [codex] Add Brevo newsletter sync for mailbox provisioning [codex] Add 508 members newsletter sync Jun 10, 2026

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

Copy link
Copy Markdown

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.

Reviewed commit: e6dce19025

ℹ️ 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 +251 to +252
except EspoAPIError:
return None

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 Fail closed when CRM block lookup errors

When EspoCRM is configured but list_contacts raises, this returns None, and the caller treats that the same as “no blocked CRM contact” because _is_crm_blocked(None) is false. During a transient CRM outage or bad CRM credentials, the weekly newsletter sync will therefore subscribe every non-excluded Migadu mailbox/recovery email, including contacts that would otherwise be skipped as inactive, rejected, or blocked; the lookup error should skip the mailbox or abort the sync instead of failing open.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 5 comments.

Comment thread apps/discord_bot/src/five08/discord_bot/cogs/migadu.py
Comment on lines +8603 to +8607
newsletter_line = (
f"\nNewsletter: subscription warning: `{exc.newsletter_error}`"
if exc.newsletter_error
else "\nNewsletter: added mailbox and backup email."
)
Comment on lines +8768 to +8771
message_lines.append(
"Newsletter: subscription warning: "
f"`{result.mailbox.newsletter_error}`"
)
Comment thread .env.example Outdated
Comment thread ENVIRONMENT.md

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

Copy link
Copy Markdown

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.

Reviewed commit: 224a385aba

ℹ️ 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 +116 to +122
if existing is not None and (
bool(existing.get("emailBlacklisted"))
or bool(existing.get("smsBlacklisted"))
or str(existing.get("status") or "").strip().casefold()
in PROVIDER_SUPPRESSED_STATUSES
):
return "skipped_provider_suppressed"

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 Skip Brevo list-level unsubscribes

When a Brevo contact has unsubscribed from only the 508 members list, Brevo returns that list ID in listUnsubscribed while emailBlacklisted remains false. This suppression check does not inspect listUnsubscribed, so the code falls through and calls add_contact_to_list, which can re-add or repeatedly attempt to re-add contacts who explicitly opted out of this list, contrary to the new “does not re-add provider-suppressed contacts” behavior.

Useful? React with 👍 / 👎.

Copilot AI review requested due to automatic review settings June 10, 2026 15:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 30 out of 31 changed files in this pull request and generated 5 comments.

Comment on lines +49 to +51
normalized_email = email.strip().lower()
if not normalized_email or normalized_email.count("@") != 1:
raise ValueError("Brevo contact email must be a full email address.")
Comment on lines +97 to +99
normalized_email = email.strip().lower()
if not normalized_email or normalized_email.count("@") != 1:
raise ValueError("Brevo contact email must be a full email address.")
Comment on lines +45 to +47
normalized_email = email.strip().lower()
if not normalized_email or normalized_email.count("@") != 1:
raise ValueError("Keila contact email must be a full email address.")
Comment on lines +66 to +68
normalized_email = email.strip().lower()
if not normalized_email or normalized_email.count("@") != 1:
raise ValueError("Keila contact email must be a full email address.")
Comment on lines +38 to +39
def ensure_contact(self, contact: NewsletterContact) -> str:
"""Ensure contact exists, returning added/updated/already/skipped."""

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

Copy link
Copy Markdown

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.

Reviewed commit: 19a831cec4

ℹ️ 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 +98 to +100
payload["data"] = {
**(existing_data if isinstance(existing_data, dict) else {}),
**(data or {}),

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 Preserve existing Keila audiences when tagging

When an existing Keila contact already has data.audiences for other segments, this merge still lets the newsletter payload from KeilaNewsletterProvider overwrite that list with only ['508_members']; fresh evidence after the earlier data-preservation concern is that the current spread order preserves top-level keys but replaces the nested audience array. A member sync would silently drop the contact's other Keila audience tags, so union the existing and new audiences before PATCHing.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 6243-6299: The current implementation sends the email via
send_onboarding_email_message before calling
_mark_dashboard_onboarding_email_sent to record the send, creating a race
condition where duplicate emails can be sent if the function is retried between
a successful SMTP send and the marker update. Refactor this by moving the marker
reservation or update to occur before the SMTP send operation. Check if the
marker already exists at the beginning of the logic after validation, and only
proceed with send_onboarding_email_message if the marker reservation succeeds.
This makes the operation idempotent so retries after a successful SMTP send will
be safely skipped.
- Around line 6372-6387: The audit metadata being passed to
_audit_dashboard_onboarding_email() contains raw email addresses in the
recipient_email and reply_to_email fields, which violates PII safety practices.
Either mask these email addresses using the existing mask_email() helper
function before including them in the metadata dictionary, or omit these fields
entirely and rely on the contact_id field for traceability. Choose the approach
that best balances debugging needs with privacy requirements.

In `@packages/shared/src/five08/settings.py`:
- Line 90: The newsletter_sync_excluded_mailboxes field in settings.py is
initialized with an empty string default, which means no mailboxes are excluded
by default. This is unsafe because downstream sync logic directly consumes this
value to determine which mailboxes to skip, potentially exposing system and
service mailbox addresses to external providers. Replace the empty string
default with a safe default value that includes common system or service
mailboxes that should be excluded from newsletter synchronization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9c56d89-0c26-46a3-831a-222c33d779bb

📥 Commits

Reviewing files that changed from the base of the PR and between 54304cf and 9786246.

📒 Files selected for processing (26)
  • .env.example
  • ENVIRONMENT.md
  • apps/admin_dashboard/src/main.tsx
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-DLefnMNV.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/worker/src/five08/worker/jobs.py
  • docs/configuration.md
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/clients/brevo.py
  • packages/shared/src/five08/clients/contact_email.py
  • packages/shared/src/five08/clients/keila.py
  • packages/shared/src/five08/clients/migadu.py
  • packages/shared/src/five08/newsletter_sync.py
  • packages/shared/src/five08/redaction.py
  • packages/shared/src/five08/runtime_config.py
  • packages/shared/src/five08/settings.py
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_brevo_client.py
  • tests/unit/test_keila_client.py
  • tests/unit/test_migadu_client.py
  • tests/unit/test_newsletter_sync.py
  • tests/unit/test_runtime_config.py
  • tests/unit/test_worker_newsletter_sync.py
✅ Files skipped from review due to trivial changes (3)
  • packages/shared/src/five08/clients/contact_email.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • ENVIRONMENT.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/api/src/five08/backend/static/dashboard/index.html
  • docs/configuration.md
  • tests/unit/test_worker_newsletter_sync.py
  • packages/shared/src/five08/clients/migadu.py
  • apps/worker/src/five08/worker/jobs.py
  • tests/unit/test_backend_api.py
  • packages/shared/src/five08/runtime_config.py
  • tests/unit/test_agent_gateway.py
  • apps/admin_dashboard/src/main.tsx
  • tests/unit/test_newsletter_sync.py
  • tests/unit/test_runtime_config.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/newsletter_sync.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 6243-6299: The current implementation sends the email via
send_onboarding_email_message before calling
_mark_dashboard_onboarding_email_sent to record the send, creating a race
condition where duplicate emails can be sent if the function is retried between
a successful SMTP send and the marker update. Refactor this by moving the marker
reservation or update to occur before the SMTP send operation. Check if the
marker already exists at the beginning of the logic after validation, and only
proceed with send_onboarding_email_message if the marker reservation succeeds.
This makes the operation idempotent so retries after a successful SMTP send will
be safely skipped.
- Around line 6372-6387: The audit metadata being passed to
_audit_dashboard_onboarding_email() contains raw email addresses in the
recipient_email and reply_to_email fields, which violates PII safety practices.
Either mask these email addresses using the existing mask_email() helper
function before including them in the metadata dictionary, or omit these fields
entirely and rely on the contact_id field for traceability. Choose the approach
that best balances debugging needs with privacy requirements.

In `@packages/shared/src/five08/settings.py`:
- Line 90: The newsletter_sync_excluded_mailboxes field in settings.py is
initialized with an empty string default, which means no mailboxes are excluded
by default. This is unsafe because downstream sync logic directly consumes this
value to determine which mailboxes to skip, potentially exposing system and
service mailbox addresses to external providers. Replace the empty string
default with a safe default value that includes common system or service
mailboxes that should be excluded from newsletter synchronization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9c56d89-0c26-46a3-831a-222c33d779bb

📥 Commits

Reviewing files that changed from the base of the PR and between 54304cf and 9786246.

📒 Files selected for processing (26)
  • .env.example
  • ENVIRONMENT.md
  • apps/admin_dashboard/src/main.tsx
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-DLefnMNV.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/worker/src/five08/worker/jobs.py
  • docs/configuration.md
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/clients/brevo.py
  • packages/shared/src/five08/clients/contact_email.py
  • packages/shared/src/five08/clients/keila.py
  • packages/shared/src/five08/clients/migadu.py
  • packages/shared/src/five08/newsletter_sync.py
  • packages/shared/src/five08/redaction.py
  • packages/shared/src/five08/runtime_config.py
  • packages/shared/src/five08/settings.py
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_brevo_client.py
  • tests/unit/test_keila_client.py
  • tests/unit/test_migadu_client.py
  • tests/unit/test_newsletter_sync.py
  • tests/unit/test_runtime_config.py
  • tests/unit/test_worker_newsletter_sync.py
✅ Files skipped from review due to trivial changes (3)
  • packages/shared/src/five08/clients/contact_email.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • ENVIRONMENT.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/api/src/five08/backend/static/dashboard/index.html
  • docs/configuration.md
  • tests/unit/test_worker_newsletter_sync.py
  • packages/shared/src/five08/clients/migadu.py
  • apps/worker/src/five08/worker/jobs.py
  • tests/unit/test_backend_api.py
  • packages/shared/src/five08/runtime_config.py
  • tests/unit/test_agent_gateway.py
  • apps/admin_dashboard/src/main.tsx
  • tests/unit/test_newsletter_sync.py
  • tests/unit/test_runtime_config.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/newsletter_sync.py
🛑 Comments failed to post (3)
apps/api/src/five08/backend/api.py (2)

6243-6299: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the SMTP send idempotent before calling the provider.

Line 6285 performs the non-idempotent email send before any durable “already sent” reservation. A double-click, retry, or crash between SMTP success and the marker update can send duplicate onboarding emails. Check/reserve the send marker before SMTP, or route this through an idempotent queued job/outbox so retries become no-ops.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 6243 - 6299, The current
implementation sends the email via send_onboarding_email_message before calling
_mark_dashboard_onboarding_email_sent to record the send, creating a race
condition where duplicate emails can be sent if the function is retried between
a successful SMTP send and the marker update. Refactor this by moving the marker
reservation or update to occur before the SMTP send operation. Check if the
marker already exists at the beginning of the logic after validation, and only
proceed with send_onboarding_email_message if the marker reservation succeeds.
This makes the operation idempotent so retries after a successful SMTP send will
be safely skipped.

6372-6387: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not persist raw email addresses in audit metadata.

This success audit stores recipient_email and reply_to_email verbatim. Use the existing mask_email() helper, or omit these fields and rely on contact_id for traceability.

Proposed PII-safe audit metadata
         metadata={
             **metadata,
-            "candidate_name": candidate_name,
-            "recipient_email": recipient_email,
-            "reply_to_email": reply_to_email,
-            "sender_display_name": sender_display_name,
-            "signature_name": signature_name,
+            "recipient_email_masked": mask_email(recipient_email),
+            "reply_to_email_masked": mask_email(reply_to_email),
             "onboarding_status": onboarding_status,
             "marker_status": marker_status,
             "marker_error": marker_error,
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    await _audit_dashboard_onboarding_email(
        session,
        result=AuditResult.SUCCESS,
        contact_id=contact_id.strip(),
        metadata={
            **metadata,
            "recipient_email_masked": mask_email(recipient_email),
            "reply_to_email_masked": mask_email(reply_to_email),
            "onboarding_status": onboarding_status,
            "marker_status": marker_status,
            "marker_error": marker_error,
        },
    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/five08/backend/api.py` around lines 6372 - 6387, The audit
metadata being passed to _audit_dashboard_onboarding_email() contains raw email
addresses in the recipient_email and reply_to_email fields, which violates PII
safety practices. Either mask these email addresses using the existing
mask_email() helper function before including them in the metadata dictionary,
or omit these fields entirely and rely on the contact_id field for traceability.
Choose the approach that best balances debugging needs with privacy
requirements.
packages/shared/src/five08/settings.py (1)

90-90: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a safe default for excluded newsletter mailboxes.

Line 90 sets newsletter_sync_excluded_mailboxes to empty, and downstream sync logic consumes this value directly to decide which mailboxes to skip. That can send system/service mailbox addresses to external providers when exclusions are not explicitly configured.

🔧 Proposed fix
-    newsletter_sync_excluded_mailboxes: str = ""
+    newsletter_sync_excluded_mailboxes: str = "system,service-account"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    newsletter_sync_excluded_mailboxes: str = "system,service-account"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/settings.py` at line 90, The
newsletter_sync_excluded_mailboxes field in settings.py is initialized with an
empty string default, which means no mailboxes are excluded by default. This is
unsafe because downstream sync logic directly consumes this value to determine
which mailboxes to skip, potentially exposing system and service mailbox
addresses to external providers. Replace the empty string default with a safe
default value that includes common system or service mailboxes that should be
excluded from newsletter synchronization.

Comment thread apps/api/src/five08/backend/api.py Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 33 changed files in this pull request and generated 1 comment.

Comment on lines +6683 to +6686
preview = await asyncio.to_thread(
NewsletterSyncProcessor(settings).sync_508_members,
dry_run=True,
)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/five08/backend/api.py`:
- Around line 6687-6702: Dry-run responses in the dashboard flow are leaking
sensitive details because the exception path only redacts email-like text and
the success path returns the raw preview. Update the
`sync_508_members`/dashboard dry-run handling in `api.py` to return a generic
failure message instead of raw exception text, and apply recursive redaction to
the `preview` payload before including it in the `JSONResponse` (for strings,
lists, and dicts, including nested `email`/`mailbox` fields). Use a helper like
`_redact_newsletter_sync_preview` near the dashboard helpers and make the
dry-run branches call it before returning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb00d1b3-937d-48fb-933f-fb14a2fd0403

📥 Commits

Reviewing files that changed from the base of the PR and between 9786246 and 209846b.

📒 Files selected for processing (13)
  • .env.example
  • apps/admin_dashboard/src/main.tsx
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-BQi7fXYh.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • packages/shared/src/five08/newsletter_sync.py
  • packages/shared/src/five08/runtime_config.py
  • packages/shared/src/five08/settings.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_newsletter_sync.py
  • tests/unit/test_runtime_config.py
  • tests/unit/test_shared_settings.py
✅ Files skipped from review due to trivial changes (2)
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/index.html
🚧 Files skipped from review as they are similar to previous changes (8)
  • tests/unit/test_shared_settings.py
  • apps/admin_dashboard/src/main.tsx
  • packages/shared/src/five08/settings.py
  • tests/unit/test_backend_api.py
  • packages/shared/src/five08/runtime_config.py
  • tests/unit/test_runtime_config.py
  • packages/shared/src/five08/newsletter_sync.py
  • tests/unit/test_newsletter_sync.py

Comment thread apps/api/src/five08/backend/api.py Outdated

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

Copy link
Copy Markdown

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.

Reviewed commit: 209846bc2a

ℹ️ 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 +1482 to +1484
mailbox["newsletter_error"] = self._add_emails_to_newsletter(
[mailbox_email, backup_email]
)

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 Gate newsletter writes behind integration scope

When the standalone mail_write.create_mailbox agent tool is authorized, its manifest still requires only mailbox:create, but this new call now writes the mailbox and backup email into Brevo/Keila. In any scoped execution or confirmation flow that relies on the manifest-required scopes, a mailbox-only action can now perform external newsletter integration writes without the integration:manage scope that account_write.create_user_accounts already requires; either keep this tool mailbox-only or require the additional scope before subscribing.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 34 changed files in this pull request and generated 3 comments.

Comment on lines +1445 to +1447
except Exception as exc:
text = " ".join(f"Newsletter sync failed: {exc}".split()).strip()
return f"{text[:197]}..." if len(text) > 200 else text
Comment on lines +225 to +231
except Exception as exc:
error_warning = _truncate_discord_text(
f"Newsletter sync failed: {exc}",
limit=500,
)
logger.warning("Newsletter sync warning: %s", error_warning, exc_info=True)
return error_warning
Comment on lines +3835 to +3841
except Exception as exc:
error_warning = self._sanitize_error_message_for_discord(
f"Newsletter sync failed: {exc}",
max_length=500,
)
logger.warning("Newsletter sync warning: %s", error_warning, exc_info=True)
return error_warning

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

Copy link
Copy Markdown

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.

Reviewed commit: 24fe493bca

ℹ️ 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 +132 to +135
if not isinstance(data, dict):
raise MigaduAPIError("Migadu response payload must be a JSON object.")

raw_mailboxes = data.get("mailboxes", [])

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 Parse Migadu mailbox index arrays

When the periodic or dashboard newsletter sync hits a real Migadu account, the Mailboxes/INDEX response is the index payload itself as a JSON array of mailbox objects, but this branch rejects any top-level list and then looks for a non-documented mailboxes wrapper. That makes configured resync jobs fail before scanning any mailbox; accept the array response shape (or support both shapes) before raising here.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 35 out of 38 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • apps/api/src/five08/backend/static/dashboard/assets/index-DE4wrrPS.css: Generated file

Comment on lines +230 to +270
def get_suppression(
self, contact: NewsletterContact
) -> NewsletterSuppressionSignal | None:
list_id = self._list_id()
if list_id is None:
return None
existing = self.client.get_contact(contact.email)
return self._suppression_from_existing(contact, existing, list_id)

def _suppression_from_existing(
self,
contact: NewsletterContact,
existing: dict[str, Any] | None,
list_id: int,
) -> NewsletterSuppressionSignal | None:
if existing is None:
return None
reasons: list[str] = []
if bool(existing.get("emailBlacklisted")):
reasons.append("email_blacklisted")
status = str(existing.get("status") or "").strip().casefold()
if status in PROVIDER_SUPPRESSED_STATUSES:
reasons.append(f"status_{status}")
if _contains_list_id(existing.get("listUnsubscribed"), list_id):
reasons.append("list_unsubscribed")
if not reasons:
return None
return NewsletterSuppressionSignal(
email=contact.email,
source_provider=self.name,
reason=_joined_reason(reasons),
metadata={"list_id": list_id, "reasons": reasons},
)

def _sync_contact(self, contact: NewsletterContact, *, write: bool) -> str:
list_id = self._list_id()
if list_id is None:
return "skipped_list_missing"

existing = self.client.get_contact(contact.email)
if self._suppression_from_existing(contact, existing, list_id) is not None:
Comment on lines +292 to +317
def get_suppression(
self, contact: NewsletterContact
) -> NewsletterSuppressionSignal | None:
existing = self.client.get_contact_by_email(contact.email)
return self._suppression_from_existing(contact, existing)

def _suppression_from_existing(
self,
contact: NewsletterContact,
existing: dict[str, Any] | None,
) -> NewsletterSuppressionSignal | None:
if existing is None:
return None
status = str(existing.get("status") or "").strip().casefold()
if status not in PROVIDER_SUPPRESSED_STATUSES:
return None
return NewsletterSuppressionSignal(
email=contact.email,
source_provider=self.name,
reason=f"status_{status}",
metadata={"status": status},
)

def _sync_contact(self, contact: NewsletterContact, *, write: bool) -> str:
existing = self.client.get_contact_by_email(contact.email)
if self._suppression_from_existing(contact, existing) is not None:
assert FakeBrevoClient.contact_lookup_emails == []


def test_sync_508_members_avoids_duplicate_keila_contact_lookups() -> None:

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

Copy link
Copy Markdown

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.

Reviewed commit: 3c35dd6d00

ℹ️ 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 +491 to +499
except Exception as exc:
self._apply_provider_status(
result,
provider.name,
contact.email,
synced_key,
error=exc,
)
continue

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 Fail closed when suppression lookup errors

When a provider's get_suppression call fails for a contact, this records a provider failure and then continues with no suppression signal, so the caller can still write that same contact to any other configured provider in the later ensure_contact loop. During a Brevo/Keila outage or bad credentials, a contact that is opted out in the failing provider can therefore be added to another newsletter tool, bypassing the provider-independent suppression behavior; skip the contact or abort the sync when suppression lookup fails.

Useful? React with 👍 / 👎.

Comment on lines +1441 to +1444
result = sync_newsletter_contacts(
self.runtime_config,
emails,
source="agent_account_creation",

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 Preserve suppression registry in agent sync

When newsletter sync is triggered through the agent tools, passing ToolRuntimeConfig here drops postgres_url, and sync_newsletter_contacts only loads newsletter_suppressions when that attribute is present. Agent mailbox/account provisioning therefore ignores active internal/manual suppression records and can re-subscribe an email that the Discord and worker paths would skip; include the DB setting in the runtime config or use the shared settings for this call.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/shared/src/five08/newsletter_sync.py (1)

417-442: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Avoid fetching provider contacts twice per sync.

get_suppression() fetches the existing provider contact, then ensure_contact() fetches it again for non-suppressed contacts. Carry the lookup result/sync plan through the provider API so weekly reconciliation and direct provisioning don’t double provider API traffic.

Also applies to: 824-877

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/five08/newsletter_sync.py` around lines 417 - 442,
`get_suppression()` and `ensure_contact()` are both fetching the same provider
contact, causing duplicate provider API calls during sync. Update the sync flow
in `newsletter_sync.py` so the lookup result from `get_suppression()` is carried
into the provider operation, and adjust the provider API used by
`ensure_contact()` to accept/use that precomputed sync plan or contact state
instead of refetching. Keep the change aligned with the `provider_suppressions`,
`_record_provider_suppressions`, and `provider.ensure_contact` path so weekly
reconciliation and direct provisioning share one provider lookup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/admin_dashboard/src/main.tsx`:
- Around line 1741-1754: The people load flow is using canUse("people:sync") to
decide whether to fetch newsletter suppressions, which incorrectly includes
dry-run-only users and triggers 403s. Update the suppression-read gating in the
main people loader and the related People card/render paths to use
can("people:sync") instead, while keeping canUse("people:sync") only for the
sync button or other dry-run-allowed actions. Apply the same split anywhere the
People load/refresh or suppression UI currently checks canUse("people:sync"),
including the affected people sync and suppression sections.

In `@apps/api/src/five08/backend/api.py`:
- Around line 6738-6739: The Newsletter sync dry-run exception handler is still
leaking sensitive details via logger.exception, so update the except block in
the dry-run flow to avoid logging the raw traceback/message. In the relevant
dry-run handler around the Newsletter sync path, replace
logger.exception("Newsletter sync dry run failed") with a generic failure log
that records only the exception type (for example via type(exc).__name__) and
keep the message free of provider/CRM payload details.

In `@packages/shared/src/five08/newsletter_sync.py`:
- Around line 403-410: The dry-run branch in newsletter_sync’s sync flow returns
before registry suppressions are loaded, so preview results can show contacts as
would_sync incorrectly. Update the dry-run path in the sync method around the
existing _preview_provider_contacts call so _load_registry_suppressions is
applied first, using the same suppression data and synced_key before returning
result. Keep the preview behavior intact, but ensure the contacts filtered by
_load_registry_suppressions match what the write path would later skip as
skipped_internal_suppression.

In `@tests/unit/test_newsletter_sync.py`:
- Around line 143-145: The fake contact-matching logic in
test_newsletter_sync.py is too permissive because it appends contacts even when
neither c508Email nor emailAddress is present. Update the matcher to mirror
production behavior by only adding contacts when there is a non-empty
intersection on email fields, using the existing contact filtering logic around
contact_values and matches so email-less CRM contacts are not returned.

---

Nitpick comments:
In `@packages/shared/src/five08/newsletter_sync.py`:
- Around line 417-442: `get_suppression()` and `ensure_contact()` are both
fetching the same provider contact, causing duplicate provider API calls during
sync. Update the sync flow in `newsletter_sync.py` so the lookup result from
`get_suppression()` is carried into the provider operation, and adjust the
provider API used by `ensure_contact()` to accept/use that precomputed sync plan
or contact state instead of refetching. Keep the change aligned with the
`provider_suppressions`, `_record_provider_suppressions`, and
`provider.ensure_contact` path so weekly reconciliation and direct provisioning
share one provider lookup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 93e50824-f786-419d-8cde-c6b2dce6f7b5

📥 Commits

Reviewing files that changed from the base of the PR and between 209846b and 3c35dd6.

📒 Files selected for processing (21)
  • apps/admin_dashboard/src/main.tsx
  • apps/api/src/five08/backend/api.py
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
  • apps/api/src/five08/backend/static/dashboard/assets/index-BoK8s4aw.css
  • apps/api/src/five08/backend/static/dashboard/assets/index-DE4wrrPS.css
  • apps/api/src/five08/backend/static/dashboard/assets/index-DQl5fG-a.js
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • apps/discord_bot/src/five08/discord_bot/cogs/migadu.py
  • apps/worker/src/five08/worker/migrations/versions/20260614_0100_create_newsletter_suppressions.py
  • packages/shared/src/five08/agent/tools.py
  • packages/shared/src/five08/clients/migadu.py
  • packages/shared/src/five08/newsletter_suppressions.py
  • packages/shared/src/five08/newsletter_sync.py
  • tests/evals/discord-agent/fixtures/v1/mailbox_create_confirmation_001.json
  • tests/unit/test_agent_gateway.py
  • tests/unit/test_backend_api.py
  • tests/unit/test_crm_create_sso_user.py
  • tests/unit/test_migadu_client.py
  • tests/unit/test_migadu_create_mailbox.py
  • tests/unit/test_newsletter_sync.py
💤 Files with no reviewable changes (1)
  • apps/api/src/five08/backend/static/dashboard/assets/index-BoK8s4aw.css
✅ Files skipped from review due to trivial changes (2)
  • apps/api/src/five08/backend/static/dashboard/index.html
  • apps/api/src/five08/backend/static/dashboard/.vite/manifest.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/unit/test_crm_create_sso_user.py
  • tests/unit/test_migadu_create_mailbox.py
  • apps/discord_bot/src/five08/discord_bot/cogs/migadu.py
  • packages/shared/src/five08/clients/migadu.py
  • tests/unit/test_agent_gateway.py
  • apps/discord_bot/src/five08/discord_bot/cogs/crm.py
  • packages/shared/src/five08/agent/tools.py

Comment thread apps/admin_dashboard/src/main.tsx Outdated
Comment thread apps/api/src/five08/backend/api.py Outdated
Comment thread packages/shared/src/five08/newsletter_sync.py
Comment thread tests/unit/test_newsletter_sync.py Outdated

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

Copy link
Copy Markdown

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.

Reviewed commit: aa583fa53a

ℹ️ 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".

synced_key,
)
self._record_provider_suppressions(provider_suppressions, result)
if active_suppressions or provider_suppressions:

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 Clear stale provider suppressions before skipping

When a Brevo/Keila contact that was previously suppressed later resubscribes or is otherwise re-enabled at the provider, provider_suppressions becomes empty but the old active registry row is still loaded here and causes the contact to be skipped forever. The new registry only ever upserts rows with active = true and there is no path that deactivates provider-observed suppressions when the provider no longer reports them, so explicit resubscriptions remain blocked by stale local state.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 35 out of 38 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • apps/api/src/five08/backend/static/dashboard/assets/index-Dtgvsgfe.css: Generated file

)
keila_api_timeout_seconds: float = 20.0
newsletter_sync_enabled: bool = True
newsletter_sync_interval_seconds: int = 604800

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

Copy link
Copy Markdown

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.

Reviewed commit: c12f5f0c33

ℹ️ 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 +577 to +579
except Exception as exc:
result["suppression_registry_warning"] = redact_email_addresses(str(exc))
return {}

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 Fail closed when suppression registry cannot be read

When postgres_url is configured but loading newsletter_suppressions fails (for example, a transient DB/query error or a missing table during deployment), this returns an empty map and the sync proceeds as if no manual/internal suppressions exist. That can re-subscribe addresses that were explicitly suppressed only in the registry; this should skip/abort the affected sync instead of treating the registry as empty.

Useful? React with 👍 / 👎.

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.

3 participants