Skip to content

Bot contact defaults: owner recipients, reply-to fallback, effective values in the console - #473

Merged
jadhavgaurav merged 4 commits into
mainfrom
development
Sep 17, 2026
Merged

jadhavgaurav merged 4 commits into
mainfrom
development

Conversation

@jadhavgaurav

Copy link
Copy Markdown
Collaborator

Why

Audit of 2026-09-17: all 8 production bots had no notification recipients, no reply-to and no business hours saved. Alerts still reached owners through a send-time fallback, but the console said "No recipient set... nowhere to go", and with no Reply-To header, replies to team and visitor emails went to notifications@oyechats.com and were lost.

What changes

Backend

  • email_service.get_reply_to_address: the saved reply-to, else the account owner's email. All 10 senders use it; an AST guard blocks direct reads.

  • New bots (POST /bots and paid per-bot checkout) save notification_emails.default = [owner email] and copy Client.company_name (not locked, so the crawl can still refresh it).

  • Migration b1000015recipientsbackfill:

    • moves legacy notification_email into notification_emails.default;
    • gives every other bot its owner's email;
    • gives Eventus (bot-2cd622e21f80) and Eval Bot (bot-7b18925d18a7) gaurav@fynix.digital.

    A read-only preview on prod shows all 8 bots get the expected address. The downgrade is a no-op. No business-hours backfill.

  • A confirmed login email change moves any default list that is still exactly the old owner address.

  • BotResponse.owner_email is exposed, and there is a new GET /bots/{id}/contact-link (the effective contact page and where it came from).

  • Impersonation sessions can no longer rewrite notification_emails.

  • Every new Client and Operator row starts with muted_push_preferences(). Google signup, superadmin-created, affiliate, invite acceptance, owner self-add and the WS owner row skipped it; an AST guard now enforces it.

Console

  • The Email panel names the account owner wherever an empty field falls back to it. The false "Empty uses the owner's address" copy is gone.
  • The panel also reads the legacy recipient field and clears it on save.
  • Tier outcomes end at the owner ("owner@x (account owner)") instead of warning "nowhere to go"; the links now go to the Email tab.
  • Business hours:
    • no schedule shows "Always available (24/7)";
    • a schedule saved without a zone shows UTC, as the server runs it;
    • a new schedule saves the browser zone.
  • Voice tab on Free:
    • pricing copy matches pricing_gate (pricing link, then contact page, then knowledge base);
    • a note names the contact page the chatbot links to.
  • New strings in en, ar and hi.

Widget

  • The fallback colour is DEFAULT_PRIMARY_COLOR (#a21caf, the API default) instead of 35 #3A0CA3 literals, with a guard test.
  • QueueWaitingScreen defaults to 60s. The component is still not mounted anywhere.

Checks

  • Backend: ruff ✓, full suite 12,487 passed (CI-faithful env).
  • Console: lint ✓, tsc ✓, vitest 2,361 ✓, build ✓.
  • Widget: lint ✓, test 444 ✓, build ✓, size ✓.

An independent review found no P0. Its two P1s (owner email change, impersonation gap) and five P2s are fixed in the second commit.

Also in this PR: 7ad3dfb (CSP style-src guidance), already on development.

The dashboard, the AI install prompt and the developer email listed only
script-src and connect-src. The widget's stylesheet loads from the script's
origin, so a site with a style-src policy loaded the widget and then showed
nothing: the app waits for that stylesheet before rendering.

The lead capture form also rendered an inline <style> with a keyframe
index.css already defines, which a style-src without 'unsafe-inline'
blocks. It is removed, and csp.spec.js now runs the widget under exactly
the documented policy.
New bots save the owner's email as the default notification recipient and
copy the account's company name. Every Reply-To now resolves through
get_reply_to_address (saved address, then the account owner), so replies
to team alerts and visitor emails stop landing in the no-reply inbox.

A migration saves a default recipient list on every existing bot: legacy
notification_email moves into notification_emails.default, other bots get
their owner's email, and Eventus and the eval bot get gaurav@fynix.digital.

The console names the account owner wherever an empty field falls back to
it, shows a schedule with no saved zone in UTC as the server runs it, says
Always available (24/7) when there is no schedule, and on Free explains the
real pricing behaviour and which contact page the chatbot links to (new
GET /bots/{id}/contact-link).

Every new account and operator row now starts with muted push events, and
the widget's fallback colour matches the API default.
A default recipient list that is still exactly the old owner address now
follows a confirmed login email change, as the send-time fallback did.
Impersonation sessions can no longer rewrite notification_emails. Alert
paths resolve Reply-To only when there are recipients, the console clears
the legacy notification_email it folds into the default list, and the
backfill guards jsonb_array_elements with CASE.
CI builds the widget with VITE_WIDGET_BASE=https://cdn.oyechats.com/, so
the app's preload hints for lazy chunks point at the CDN rather than the
preview server, and a 'self'-only policy reported them as violations. The
spec now uses the documented origin plus 'self' for the local preview.
@jadhavgaurav
jadhavgaurav merged commit c5a0f2d into main Sep 17, 2026
6 checks passed
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