Run your workspace
Inbox & human takeover
The Inbox is your lead-and-takeover console. /app/inbox
lists every captured lead; opening one shows the full conversation
transcript that produced it and lets you jump in as a human operator
to continue the thread.
Layout
/app/inbox shows leads on the left, sorted by recency.
Click one and the end-hand pane shows the conversation that
produced it: visitor messages on one side, agent replies on the
other, human-agent messages from past takeovers in their own role
(user / assistant / human-agent).
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. From there you can
open a conversation and use the same takeover controls.
Live updates
Every open conversation subscribes to its private Reverb channel
(conversation.{id}) for real-time updates. New messages
appear without polling; the takeover state propagates to both the
visitor's widget and any other operator looking at the same thread.
Taking over
Click Take over. A few things happen:
- The conversation gets
claimed_by_user_id+claimed_atset to you (route:POST /app/conversations/{conversation}/claim). - A
conversation.claimedReverb event fires on the conversation's private channel โ the visitor's widget shows "Human is here" in the chat header. - The AI is paused. Every visitor message routes to the inbox; every reply you type streams to the visitor as a
human-agentrole message viaPOST /app/conversations/{conversation}/reply.
Releasing
Click Hand back to bot to release (route:
POST /app/conversations/{conversation}/release). The next
visitor message goes through the RAG pipeline again. The visitor's
chat header flips back to the agent's persona. 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/inbox. - Webhook-driven โ every captured lead fires the
lead.capturedoutgoing 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 inbox row.
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
Every captured lead fans out to every workspace owner and admin
over email. 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:
-
A queue worker is running. In production we use the
databasedriver โ make surephp artisan queue:work --queue=defaultruns on a process supervisor (the in-cluster worker takes care of this on Laravel Cloud). Without it, queued notifications pile up injobsand never send. -
MAIL_MAILER+ matching credentials are configured in.env. The default islogโ fine for dev but no actual email is sent. Switch tosmtp/resend/postmarkin production and verify withphp artisan tinker --execute 'Mail::raw("ping",fn($m)=>$m->to("[email protected]")->subject("test"));'.
Recipients are filtered down to workspace_users.role IN
('owner', 'admin') with accepted_at IS NOT NULL.
Pending invites and viewers do not receive lead emails. 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.
-
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 wipes every conversation in the workspace with no user-role message. Sessions where the visitor sent at least one message are never touched.
-
Bulk delete selected. Tick the checkboxes on
any visible rows, then click Delete N selected.
The workspace global scope on
Conversationprotects 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
Privileged actions on conversations (claim, release) write rows to
the audit_logs table for forensic traceability. There's
no UI page for browsing them in v1; query the table directly when
you need to investigate.