B Blengi docs

Run your workspace

Leads & human takeover

Leads (/app/leads; it was called Inbox, and /app/inbox still forwards here) lists every captured lead. Opening one shows the lead's details and status, and the conversation that produced it, to read. To take the chat over or reply, Open conversation opens the conversation screen — the one place a team member takes over a chat.

A strip above the list shows how many leads are New, Qualified, Contacted, Won and Lost across all of them (the lead pipeline that used to be on the Dashboard). Each count opens the list filtered on that status. Home announces leads that are still New.

The period control — All time (the default), Last 7 days, 30 or 90 (?period=7d|30d|90d) — narrows the list, the status counts and the totals to leads captured in those days: today and the days before it, from midnight, the same stretch as Conversations and Results. Home's New leads tile opens Leads with the last 7 days, so the list shows the number on the tile. A lead sent from a test chat in your dashboard carries a Test label: it is listed so you can check the form and the email, but Home and Results do not count it.

Leads and Conversations can be filtered to one AI employee: a chip names it, links back to it and removes the filter. This replaced the separate lists each AI employee used to have; their addresses (/app/agents/{id}/leads and /app/agents/{id}/conversations) open the filtered lists. The figures of the filtered list count that AI employee only.

Layout

/app/leads lists leads, newest first. Opening one shows the conversation that produced it on the left — visitor messages on one side, the AI employee's replies and past human replies on the other — with Open conversation and, when someone is in the chat, who that is. The lead's email, name, phone, status and where it was delivered sit on the right. The conversation screen links back with Open lead in its side panel.

The Status column is an inline select — change it in place to move a lead through new → qualified → contacted → won / lost. The list also auto-refetches when the tab regains focus or you navigate back from a conversation thread, so a status edit you made elsewhere shows up without a manual reload.

The conversations index at /app/conversations covers every conversation regardless of whether it produced a lead — useful for spelunking past sessions that didn't convert.

Live updates

Updates arrive by polling, not over websockets. An open conversation screen reloads its messages every 2 seconds while the chat is waiting for a person or taken over, and every 8 seconds otherwise (only while the tab is visible; clicking the Live pill pauses it). The visitor's widget asks for new team replies and the takeover state every 3 seconds while it is open. The server does broadcast events on private channels when a broadcast driver is configured, but no screen subscribes to them.

Taking over

On the conversation screen, click Take over. (Until card #667 the lead page had its own Take over, Hand back and reply box; they are gone, so there is one screen with one set of words.) A few things happen:

  1. The conversation gets claimed_by_user_id + claimed_at set to you (route: POST /app/conversations/{conversation}/claim).
  2. On its next 3-second poll the visitor's widget sees the takeover: it shows a short "<Name> joined the chat" notice ("An agent joined the chat" when names are hidden) for about 8 seconds, and a Live agent pill in the chat header for as long as you hold the chat.
  3. The AI is paused. Every visitor message waits for you in the conversation. Each reply you send (POST /app/conversations/{conversation}/reply) is stored as a message with role assistant and model human:<your user id>; the widget picks it up on its next poll and shows it as a team member's bubble.

Releasing

Click Hand back to release (route: POST /app/conversations/{conversation}/release). The next visitor message goes through the RAG pipeline again. The chat gets the line "The operator has stepped away. The AI assistant is back to help.", and the widget drops the Live agent pill on its next poll. Useful when:

  • You answered the off-script question and the rest is back to FAQ territory.
  • The visitor is satisfied and likely to leave.
  • You're ending your shift — handing back keeps coverage 24/7.

Capturing leads

Leads come in two ways:

  • Visitor-driven — the widget's inline lead form, fired by a behavior rule or by the visitor explicitly asking to be contacted. Submitted leads land in /app/leads.
  • Webhook-driven — every captured lead fires the lead.captured outgoing webhook so you can fan it into your CRM. See Outgoing webhooks.

Live in-app toasts

The admin shell polls GET /app/leads/feed every 30 seconds for newly captured leads in the workspace and surfaces each one as a sonner toast in the bottom-right of every page in the admin SPA. Click the toast to jump straight to the lead.

The bell button in the top header asks the browser for native notification permission. Once granted, every new lead also fires an OS-level notification so workspace members get pinged on tabs that aren't focused. Permission is per-domain — denying it once can only be reversed from your browser's settings.

Polling pauses on hidden tabs to keep idle dashboards from burning HTTP. The cursor lives in sessionStorage so opening a second tab doesn't double-toast already-seen leads.

Email notifications

By default every captured lead is emailed to every workspace owner and admin. Per AI employee, Lead capture › Who is told about a new lead can add up to 10 extra addresses (they need no account) and switch off Also send to the workspace owners and admins, so only those addresses get it. The notification (App\Notifications\NewLeadCaptured) is queued — the visitor's HTTP request never waits on SMTP, so a slow mailer cannot slow lead capture or chat.

Two requirements for the email to actually arrive:

  1. A queue worker is running. In production we use the database driver — make sure php artisan queue:work --queue=default runs on a process supervisor (the in-cluster worker takes care of this on Laravel Cloud). Without it, queued notifications pile up in jobs and never send.
  2. MAIL_MAILER + matching credentials are configured in .env. The default is log — fine for dev but no actual email is sent. Switch to smtp / resend / postmark in production and verify with php artisan tinker --execute 'Mail::raw("ping",fn($m)=>$m->to("you@example.com")->subject("test"));'.

Team recipients are filtered down to workspace_users.role IN ('owner', 'admin') with accepted_at IS NOT NULL. Pending invites, editors and viewers do not receive lead emails unless their address is one of the extra recipients; an extra address that already belongs to such an owner or admin gets one mail, not two. The footer of the email reflects your white-labelled site title (set in Settings → System).

Cleaning up the conversation log

A new Conversation row is written every time a visitor loads the widget for the first time in 24 hours. Visitors that drive by without typing still produce a row, which means /app/conversations can pile up with empty sessions over time. Two affordances keep it manageable:

  • Engaged-only filter (default). The list hides conversations with no visitor messages. Toggle Show all in the filter bar to see every session — useful when you're looking for bot traffic or QA loads. The dashboard's Conversations tile and the agents list count with the same rule, so the numbers agree with this list. Under Needs you and Live now every matching session shows, also one where the visitor asked for a person before typing anything (the button in the chat header works straight away): the pills and Home count those, so the list shows them too.
  • Period filter. All time / Last 7 days / Last 30 days / Last 90 days (?period=7d|30d|90d) bounds the list on the conversation's start time and is kept across search, the pills, the engaged toggle and pagination. Last 7 days is today and the six days before it, from midnight — the same stretch as Results and Home's This week. The count next to the control is the number of conversations in that window under the current filters — the quickest answer to "how many real conversations did we have this week". Unknown values fall back to all time, so an old bookmark never errors.
  • Per-row delete. Admin and Owner roles see a trash icon on hover. Deleting cascades to messages, leads, and applied tags via DB foreign keys; analytics events are kept (FK nullOnDelete) so aggregate stats stay intact.
  • Bulk delete empty. The button in the filter bar deletes the empty sessions it counts: those of the AI employee the list is filtered to (?agent=), or of the whole workspace when it is not, and the confirmation says which. A session is empty when the visitor sent no message, did not ask for a person and no team member took it over. Sessions where the visitor wrote, or is waiting for a person, are never touched.
  • Bulk delete selected. Tick the checkboxes on any visible rows, then click Delete N selected. The workspace global scope on Conversation protects cross-tenant ID smuggling — IDs from other workspaces are silently dropped server-side.

Viewers and Editors do not see delete affordances; the ConversationPolicy::delete + WorkspacePolicy::bulkDeleteConversations checks require Admin or Owner. Deletion is irreversible — there is no soft-delete trail and the audit log does not yet capture conversation removals.

Audit log

Taking over and handing back (claim, release) do not write audit_logs rows. The thread itself shows the hand-back line and, after a transfer, an internal note naming both team members. Platform staff can browse a workspace's audit log at /admin/workspaces/{workspace}/audit; there is no audit page for customers.