Skip to main content

# Notifications

CoderFlow can reach users through email (SMTP) and browser desktop notifications (foreground + Web Push). Both are off by default until configured: the server doesn't send email until SMTP is set up, and a browser doesn't receive desktop notifications until the user grants permission.

This page covers the server configuration, the per-user preferences each channel honors, and the @-mention syntax that drives most comment emails.

## Channels at a glance​

Channel | Triggers | Configured by | Per-user opt-out
--- | --- | --- | ---
Email — @mentions | Someone mentions you in a comment | Admin: SMTP | Profile → Notifications → @Mentions
Email — comments on your tasks | Someone comments on a task or objective you created | Admin: SMTP | Profile → Notifications → Comments on my tasks/objectives
Desktop notifications (foreground) | A task you launched in this browser tab finishes | Per-user, per-browser permission | Toggle off Browser Notifications → Enable on this browser
Web Push (background) | A task you launched finishes while the tab is closed/backgrounded | Auto-enabled when desktop notifications are granted | Same as above (revoking the toggle unsubscribes)

The server stores per-user preferences (`email_mentions`, `email_comments_on_my_tasks`, `task_completion_notification_mode`) on the user record; the browser-permission toggle is per-device and lives in `localStorage`.

## Email (SMTP) Configuration​

Navigate to Settings → Email (SMTP) to configure outbound mail:

- SMTP Host — Your mail server hostname (e.g., `smtp.office365.com`)
- SMTP Port — Typically `587` for STARTTLS or `465` for SSL/TLS
- Use TLS/SSL — Enable for port 465; leave disabled for port 587 with STARTTLS
- Username — SMTP authentication username
- Password — SMTP authentication password (leave blank when re-saving to keep the existing one)
- From Address — Email address shown as the sender
- From Name — Display name shown alongside the sender address (optional)

Use Test Connection to verify the SMTP server is reachable, then Send Test Email to confirm full delivery to a recipient you specify.

SMTP settings can also be supplied via environment variables (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_SECURE`, `SMTP_FROM`, `SMTP_FROM_NAME`). The Settings UI takes precedence: when both are set, the saved configuration is used.

### Provider-specific notes​

Office 365. Use `smtp.office365.com` on port `587` with TLS/SSL disabled (STARTTLS is negotiated automatically). Ensure the sending account has SMTP authentication enabled — you may need to turn on "Authenticated SMTP" in the Microsoft 365 admin center.

Gmail. Use `smtp.gmail.com` on port `587` with TLS/SSL disabled. Gmail requires an App Password rather than the regular account password. Generate one at Google Account → Security → App Passwords; 2-Step Verification must be enabled on the account.

SendGrid. Use `smtp.sendgrid.net` on port `587`. The username is literally `apikey`; the password is the SendGrid API key.

AWS SES. Use `email-smtp.<region>.amazonaws.com` on port `587` (replace `<region>` with your AWS region). SES SMTP credentials are different from your AWS access keys — generate them in the AWS SES console under SMTP Settings, and verify the From address before sending.

### Troubleshooting​

Connection timeout. Verify the SMTP host and port. Check that your firewall allows outbound connections on the SMTP port; some cloud providers block port 25, in which case use 587 or 465.

Authentication failed. Confirm the username and password. For Gmail, use an App Password. For Office 365, confirm SMTP authentication is enabled for the account.

Emails not received. Check the spam/junk folder. Verify the From address is authorized to send from your mail server. For SES, confirm the From address is verified.

Certificate errors. If your mail server uses a self-signed certificate, the container needs to trust it. Confirm the server's system clock is accurate — certificate validation depends on it.

## Browser & Push Notifications​

CoderFlow can show desktop notifications when a task finishes — both while a CoderFlow tab is open (foreground) and after it's closed (Web Push). Both paths use the same service worker so the click handler routes to the right task page either way.

### How it works​

- The service worker (`/sw.js`) is registered the first time a user enables notifications, and it stays registered across visits.
- The browser displays the notification with `ServiceWorkerRegistration.showNotification()`, including on Android Chrome where the `new Notification()` constructor throws.
- For background delivery, the browser subscribes to the server's Web Push endpoint (`POST /api/push/subscribe`) using a VAPID public key from `GET /api/push/vapid-key`. The server then dispatches push payloads to that subscription whenever the user's tasks reach a terminal status.
- Notification clicks open `/task.html?id=<taskId>` (or `?groupId=...` for groups). If a tab on that URL is already open, it gets focused instead of opening a new one.

### VAPID configuration (admin)​

Web Push requires a VAPID key pair. CoderFlow loads keys with this priority:

1. `VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY` environment variables.
2. Persisted keys at `${DATA_DIR}/.vapid-keys.json`.
3. Auto-generated on first start, persisted to that file with `0600` permissions.

In other words, Web Push works out of the box without manual setup — you only need to set the env vars to pin a specific keypair (e.g. across replicas or for disaster recovery).

The VAPID subject defaults to `mailto:admin@coderflow.ai` and can be overridden with `VAPID_SUBJECT` (it appears in push requests so the push service has a contact for abuse reports). If the `web-push` npm package isn't installed in the running build, Web Push is disabled silently and `GET /api/push/vapid-key` returns `503 push_disabled`; foreground notifications still work in that case.

Per-user push subscriptions are stored at `${DATA_DIR}/push-subscriptions.json`. When a push delivery returns `404` or `410`, the stale endpoint is pruned automatically.

### User experience​

The first time a logged-in user lands on the home page, a banner appears:

> Enable Desktop Notifications — Stay informed when your tasks complete

Clicking Enable Notifications asks the browser for permission, registers the service worker, and (if VAPID is configured) registers a push subscription. The same toggle lives at Profile → Notifications → Browser Notifications → Enable on this browser. The Dismiss button hides the banner permanently for the current browser via `localStorage`.

The browser-notification preference is per-device, not per-account: it follows the browser's permission and a `localStorage` flag, so enabling it in Chrome on a laptop does not automatically enable it in Firefox on a phone. The profile checkbox reflects the local state, not a server-stored preference.

A push endpoint identifies a specific browser+VAPID pair, not a user. If a different account logs in to the same browser and re-subscribes, the server transfers ownership to the new account — so notifications follow whoever is currently logged in on that device.

### Foreground vs. background, group vs. per-task​

When the user has notifications enabled, the client subscribes to the server-sent task event stream. Each terminal task event triggers either a foreground notification (if a CoderFlow tab is open and tracking it) or a Web Push delivery (otherwise).

The Grouped task runs preference at Profile → Notifications controls how groups behave:

- Notify each task as it finishes (default) — Each task in a group fires its own notification.
- Notify once when the group finishes — Standalone tasks still notify when they finish, but multi-task groups suppress per-task notifications and emit a single summary notification once every task in the group reaches a terminal state.

Standalone tasks always notify when they finish, regardless of this preference. Test runs and deployment runs use a similar payload format (e.g. `Test: <name>`, `Deploy: <profile>`).

### Service worker & offline page​

The service worker also caches a small offline fallback page (`/offline.html`). When a top-level navigation fails because the server can't be reached, the browser shows that page with a Reload button. Other requests (XHR, fetch, assets) are not cached and will fail normally. There is no general background-sync queue: CoderFlow does not retry failed API calls when connectivity returns.

## Per-user notification preferences​

Each user controls their own notification settings at Profile → Notifications. Defaults apply when a user has never edited them.

- Email Notifications
  - @Mentions (default: on) — Email when mentioned in a task or objective comment.
  - Comments on my tasks/objectives (default: on) — Email when someone else comments on a task or objective you created.
- Browser Notifications
  - Enable on this browser (default: off; per-device) — Toggle desktop notifications and Web Push for this browser. Disabling unsubscribes from Web Push immediately.
  - Grouped task runs (default: Notify each task) — Per-task vs. per-group desktop notifications, as described above.

A user must have an email address on their profile to receive any email notification. SMTP must also be configured globally; otherwise email is skipped silently and only logged at debug level.

The server preferences are stored on the user record under `preferences` (`email_mentions`, `email_comments_on_my_tasks`, `task_completion_notification_mode`). Two adjacent profile preferences live in the same area but are not notifications:

- Default Environment — Used when a user creates a task without picking one explicitly.
- Auto-pin new tasks (default: on) — When enabled, tasks the user creates are pinned automatically. The setting is mirrored to a server preference (`auto_pin_new_tasks`) so server-side flows like Slack-triggered tasks honor it.

## @-Mentions in comments​

Typing `@` in any comment box opens a user-search dropdown when the current user can read the user directory. Type to filter, then press Enter or click to insert. The dropdown closes if you type a space or move the cursor outside the active mention. See Permissions for the current user-directory access behavior.

In the stored markdown, mentions are encoded as `@[Display Name](mention:<userId>)`, which renders to a styled chip in the UI and to plain `@Name` in plain-text email bodies.

When a comment is posted:

1. The server parses each `@[Name](mention:userId)` token in the content.
2. For every mentioned user other than the commenter, it looks up the user, checks their `email_mentions` preference, and (if email is configured and the user has an email) sends the Mention notification template with a preview of the comment and a deep link back to the task or objective.
3. Independently, if the task or objective has an owner other than the commenter, the server sends the Comment notification template to the owner — gated on their `email_comments_on_my_tasks` preference.

A commenter never gets emailed about their own comment, even if they `@`-mention themselves. To opt out of mention emails, turn off Profile → Notifications → @Mentions; to opt out of being notified about replies on your own tasks and objectives, turn off Comments on my tasks/objectives.

Mention notifications and owner notifications are independent: the same comment can trigger both (one mention email, one owner email) or neither. Both depend on SMTP being configured globally.