---
title: Feedback channel opt-out (silence outbound email per install)
---

# Feedback channel opt-out (silence outbound email per install)

## What it is

Three user-level toggles at **Settings → Diagnostics → Outbound email** that let you silence the email leg of Omniscio's outbound reports for **just your install**. They do not change anyone else's install. All are **on by default** so an upgrade or a fresh install behaves identically to today — every user-clicked bug / feedback / QW-miss / overlay report still emails the team, and every crash + weekly digest still goes out by email — until you flip yours off.

The on-disk fallback (`<userData>/bug-reports/...json`) is untouched by these toggles — it is local storage, not egress, so a deliberate opt-out never loses your report. The bug/feedback toggle gates both remote legs — Resend and Firestore (see the note below). The two telemetry-email toggles still govern their own email legs only.

> **Changed by the privacy audit.** This note used to read "email off does not mean
> nothing is sent": in-app bug/feedback delivery fanned out to Resend (email) AND Firestore,
> and `bugReportEmailEnabled` gated the **Resend leg only** — so an opted-out report still reached
> the team via Firestore, and Firestore's only gate (`bugReportTransport`) was absent
> from every settings schema, so nothing could flip it. That was a consent control that did not
> withdraw consent. **Now `bugReportEmailEnabled` gates both legs**, `bugReportTransport` is
> reachable through `updateSettingsSchema` (and still chooses BETWEEN email and Firestore for a user
> who IS sending), and an opted-out report is kept on the local disk fallback instead.

The three toggles are:

| Toggle                                     | Setting key              | Gates                                                                                                                                                                                                                                                                                                                  |
| ------------------------------------------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Send bug reports & feedback**            | `bugReportEmailEnabled`  | **BOTH** remote legs of in-app bug/feedback delivery — Resend (email) and Firestore — gated in one place, `deliverReport` (report-delivery.ts): the feedback path (toolbar bug-icon clicks + the CLI `POST /feedback` route) and the bundle path (overlay "Report bad overlay" + QW-miss right-click "Should have rendered as QW" + the CLI `POST /feedback/bug-report` route) now run the SAME chain. Off ⇒ local disk fallback only. |
| **Email diagnostics to the team** (master) | `telemetryEmailEnabled`  | The entirety of `sendTelemetryEmail()` — `[MC Crash]` alerts plus the once-a-week `[MC Digest]` rollup. Off silences BOTH.                                                                                                                                                                                             |
| **Email crash reports** (crash sub-gate)   | `crashAlertEmailEnabled` | ONLY the `[MC Crash]` email leg inside `reportCrash`. Off drops crash mail while the weekly digest (still gated by the master) keeps going. A crash email sends iff **both** `telemetryEmailEnabled !== false` **and** `crashAlertEmailEnabled !== false`.                                                             |

They are split so you can tune the noise: crash + digest emails are auto-fired by the runtime (noisier during a crash loop) while user-clicked bug reports are deliberate and signal-rich. The crash sub-gate exists so you can **drop crash mail while keeping the weekly digest** — the exact case for a user whose crashes already auto-triage inside Omniscio (via the [Sentry intake path](bug-report-intake.md)), so the per-crash email is redundant. Turning off crash mail never affects Sentry or that triage — the crash still gets investigated; you just stop getting the email.

## Where to find it

### Where they live in Settings

Open **Settings → Diagnostics → Health** and the rows appear grouped under an **"Outbound email"** heading. Each is a toggle with helper text spelling out what it gates and what survives when you turn it off:

> **Send bug reports & feedback** — When on, clicking the bug-report icon, sending feedback, or reporting a missed question widget sends the report to the Omniscio team by email and to the report database. Off sends nothing: reports stay on this computer and go out when you turn this back on.
>
> **Email diagnostics to the team** — Master switch for Omniscio's diagnostic emails from this install. On = the weekly usage digest, plus each crash unless you turn off "Email crash reports" below. Off silences both; the on-disk crash log, Sentry, and Omniscio triage keep working.
>
> **Email crash reports** — When diagnostics email is on, also email the team on each crash. Turn OFF to email only the weekly digest — crashes still auto-triage inside Omniscio (via Sentry), and the on-disk crash log continues.

They are also reachable from Settings search (Ctrl+K). Useful keywords include "resend", "feedback email", "crash email", "weekly digest email", "telemetry email", "email crashes", "stop crash emails", and "outbound email".

## How it behaves

### What happens when you flip a toggle off

Both toggles share the same gating idiom: the runtime reads the field as `getSettings().<field> !== false` (or `=== false` for the early-return path). Only an **explicit `false`** changes behavior — `undefined` and `true` both keep the email path firing.

### Flipping `bugReportEmailEnabled` off

- Both remote legs are skipped at once — Resend and Firestore. `deliverReport` (report-delivery.ts) checks `plan.optedOut` first and returns immediately when it's true, so no leg is even attempted: nothing lands in either the delivered or the failed set, and a deliberate opt-out is never counted as a delivery failure.
- The report is not lost — it goes to the on-disk fallback at `<userData>/bug-reports/...json`. Disk is local storage, not egress, so the opt-out does not forbid it.
- `bugReportTransport` still chooses BETWEEN email and Firestore for a user who IS sending, and is now reachable through the settings schema (it never was before).

### What a report carries, and what switching it off therefore withholds (2026-09-29)

Every in-app bug / feedback report also carries a **changed-from-default export of your settings**: the settings you have moved away from their shipped defaults — including the ones that have no row on any Settings screen — plus a one-line "is it set?" marker for each credential you have configured. `bugReportEmailEnabled` governs it too, so switching reports off withholds this along with everything else, and it rides the same `cloud-intake` consent purpose.

- **No credential VALUE is ever included.** A configured key reads as `<set>`; an unconfigured one is simply absent from the export. The value itself never leaves the machine.
- **Only what you changed.** A setting left at its default is not listed, so the export stays readable instead of shipping ~2,000 no-op rows. On a fresh install with nothing configured it is empty, and the section is not rendered at all.
- **You can see it before you send.** The report dialog shows how many settings are attached, and the feedback consent names the settings among the categories it covers.
- **It is bounded**, so a heavy configuration can never be the reason a report fails to send; the operator email previews long values and the full value rides in the stored copy of the report.
- **Two reports are filed with no dialog at all** — a project folder that fails to open, and a snooze whose time cannot be read. Those carry the settings too, under the same consent purpose and the same off switch.

### Flipping `telemetryEmailEnabled` off (the master — silences BOTH crash + digest)

- `sendTelemetryEmail()` returns immediately on its first statement — before it relays anything to the bug-report Cloud Function, so **no send is even attempted**. (The app no longer holds a Resend client at all; the relay function does the actual send — see "Recipient routing" below.) This gates BOTH the crash email and the weekly digest.
- A single diagnostic line lands in the main log: `[Telemetry] Suppressed (telemetryEmailEnabled=false): <subject>`.
- The on-disk crash log and Sentry-side delivery keep firing — crash forensics are not gated by this toggle.

### Flipping `crashAlertEmailEnabled` off (drop crash mail, KEEP the digest)

- `reportCrash()` returns early — right after the `getCrashAutoSendEnabled()` gate and **before** the throttle — so the crash EMAIL is skipped and never even consumes a daily email slot. Only the crash email; nothing else.
- `reportFleetCrash` has already fired (it runs first, before the gate) and the Sentry `captureException` lives in the crash **handlers** (not `reportCrash`), so the crash **still reaches Sentry → Omniscio triage**. Turning off crash mail never stops crashes from being investigated.
- The weekly digest never calls `reportCrash`, so it is completely unaffected — this is the whole point of the sub-gate.
- Because the crash path still passes through `sendTelemetryEmail` (which keeps its own `telemetryEmailEnabled` check), a legacy `telemetryEmailEnabled=false` opt-out still suppresses crash mail — no migration was needed to preserve it.

### Migration semantics — none required

No one-shot migration writes anything to existing users' `config.json` from this code path. The defensive read handles all four cases correctly:

| Scenario                     | Disk state                   | `getSettings()` reads                           | Result                             |
| ---------------------------- | ---------------------------- | ----------------------------------------------- | ---------------------------------- |
| Fresh install                | no `config.json`             | `DEFAULT_SETTINGS.bugReportEmailEnabled = true` | `true !== false` → enabled ✅      |
| Existing install pre-upgrade | `config.json` exists, no key | `undefined`                                     | `undefined !== false` → enabled ✅ |
| User flips toggle off        | key is `false`               | `false`                                         | `false !== false` → disabled ✅    |
| User flips back on           | key is `true`                | `true`                                          | `true !== false` → enabled ✅      |

There is no way to opt out for everyone — no env var, no master kill switch, no migration that hand-edits anyone else's `config.json`. The toggle is the only path to `false`, and it only ever writes to the current install's own config.

### How to silence your install

1. Open **Settings → Diagnostics → Health**.
2. Toggle **Email bug reports & feedback** off if you don't want the team emailed on your bug-icon / overlay-report / QW-miss / `/feedback` clicks.
3. Toggle **Email diagnostics to the team** off if you don't want ANY diagnostic emails — crashes AND the weekly digest — from your install.
4. Or, to stop ONLY crash emails while keeping the weekly digest, leave the master on and toggle **Email crash reports** off — crashes still auto-triage inside Omniscio via Sentry.

That's it. Flip any back on the same way. No restart required — the next report after each flip respects the new state.

### What the toggles do NOT change

- **The Sentry upload path on crashes.** Crash forensics are a separate channel.
- **The on-disk `<userData>/bug-reports/...json` fallback.** It fires when every ENABLED sink fails — and, whenever the user has switched sending off, which is how an opted-out report is preserved locally rather than lost.
- **Recipient routing.** These toggles never touch _where_ mail goes — only _whether_ it's sent. Recipients are decided **server-side by the bug-report Cloud Function**, not by the app: the app only tags each send with a `leg` (`telemetry` for crash/digest → the owner's Gmail only; `feedback` for bug reports → the owner's Gmail **and** the AgentMail intake inbox). The app never specifies an address, so there is no recipient to change here.
- **The relay's credentials, or any other install's behavior.** The Resend API key now lives **server-side in the bug-report Cloud Function**, not in the app — a toggle only decides whether this install relays a send at all. Every other install (the CEO's, Steve's, every tester's) is unaffected.

## Related

- [cli-feedback.md](cli-feedback.md) — `POST /feedback` route covered by `bugReportEmailEnabled`.
- [plain-speak-feedback.md](plain-speak-feedback.md) — overlay "Report bad overlay" path covered by `bugReportEmailEnabled`.
- [qw-miss-reporting.md](qw-miss-reporting.md) — "Should have rendered as QW" path covered by `bugReportEmailEnabled`.
- [logs-and-debugging.md](logs-and-debugging.md) — automatic crash reporting (separate channel; not gated by these toggles).
