Live · In progress

Portfolio Admin CRM

A private operations workspace that turns website conversations and enquiries into an organized pipeline for contacts, follow-up, proposals, invoices, appointments, and outbound work — now with per-channel AI assistants that can draft or send email replies under explicit safety rules.

In progressPrivate CRMMulti-assistant AIGoverned email automationNext.js + Supabase
Personal buildProject
Lead-to-deliveryScope
Next.js / SupabaseStack
In progressStatus
25capability areas
11technical layers
28screens documented
5providers mapped
Overview

One private workspace for the work between the tools.

Portfolio Admin is a full-stack admin portal for the portfolio site's lead intake and client operations. It connects chat, forms, contacts, email, proposals, invoicing, scheduling, AI assistants, integrations, notifications, and job discovery through a single authenticated workspace.

The Problem

Website enquiries arrived through different channels and needed to be reconciled manually. Conversation history, contact details, follow-up, documents, appointments, and outbound opportunities could easily become separate threads with no shared operational view — and email in particular sat unanswered for hours because every reply required a person at a keyboard.

The Approach

Built a private Next.js App Router portal over Supabase Auth, Postgres, RLS, and Realtime. Legacy widget records and newer multi-channel conversations are normalized in the UI, while server routes handle email, AI, PDFs, calendar synchronization, and protected automation. AI was then split into independent per-channel assistants so the email assistant and the website chat assistant can carry different models, prompts, knowledge, and automation limits.

The Result

A connected operating surface for the complete journey: capture a lead, understand the conversation, maintain the contact record, approve or send a reply, schedule the next step, prepare a proposal or invoice, and keep the human in control. Automation is opt-in per assistant, bounded by category, confidence, and takeover rules, and every connected service is visible from one Integrations screen.

Operating Flow

From first message to next action.

The product is organized around the moments that usually create context switching for a solo operator or small team.

1

A visitor submits the website chat or a contact form, or an admin adds or imports a contact.

2

The CRM resolves the person against existing email or phone data and keeps the enquiry linked to the contact.

3

Messages, form details, scheduling requests, and notifications appear in the appropriate operational workspace, and the assistant that owns the channel classifies and summarizes the thread.

4

Depending on that assistant's automation mode, an eligible email either becomes an AI draft waiting for approval or is sent automatically after every safety check passes.

5

The admin can approve, edit, regenerate, or reject the draft, reply manually, take over from automation, or move the conversation toward a booked appointment.

6

A connected service catalog supports proposal and invoice line items, with editable records and PDF output.

7

Notifications, realtime updates, Google Calendar synchronization, and the separate job-hunt pipeline keep follow-up visible.

Core Capabilities

25 connected capability areas

Everything below is grounded in the current CRM source. Filter by area or expand everything; each card shows the implemented behavior and the boundaries that remain part of the active build.

1. Operations dashboard Leads & Contacts
  • Summarizes active contacts, messages and unread items, form submissions, proposals, and invoices.
  • Separates useful operational states such as new submissions, proposal drafts, overdue invoices, and outstanding balances.
  • Builds a recent-activity feed from chats, forms, imported contacts, proposals, and invoices instead of showing isolated module histories.
  • Provides a needs-attention view so follow-up work is visible when the admin first opens the system.
  • Uses permission-aware navigation for Dashboard, Messages, Forms, Contacts, Calendar, AI Assist, Proposals, Invoicing, Job hunt, Settings, and User Management.
2. Contact lifecycle and deduplication Leads & Contacts
  • Maintains a deduplicated contact record for people originating from the chat widget, forms, CSV import, or manual entry.
  • Searches by name, primary or secondary email, and phone, with source filters for chat widget, forms, or both.
  • Shows first-seen and last-seen context, source, company, contact methods, timezone, linked conversations, and linked form submissions.
  • Add and edit flows use protected database functions and match on email or phone so contact history and tags stay attached to the person.
  • Timezone can be saved from the contact detail surface and reused when scheduling an appointment.
See this screen →
3. Import, tagging, and bulk lifecycle tools Leads & Contacts
  • Imports CSV files with full name, email, phone, and optional secondary email fields.
  • Reports created, merged, skipped, warnings, and errors after an import, with a downloadable template for consistent input.
  • Supports searchable tags, tag creation, tag management, OR-based tag filtering, and bulk add or remove actions.
  • Allows filtered selection, bulk soft-delete, individual soft-delete, and a short-lived Undo restore action rather than hard-deleting contacts in the UI.
  • Import, tagging, and delete controls are permission-gated so the workspace can be shared without giving every user the same destructive access.
4. Form submission review Leads & Contacts
  • Reviews contact submissions in a dedicated Forms workspace with status filters, totals, new-count indicators, and an inspection modal.
  • Opening a new submission can move it to read, while the admin can explicitly change its contact status as the enquiry progresses.
  • Displays the original message, budget, requested services, contact details, and preferred call date, time, and timezone.
  • Converts a requested local time to a comparable UTC value for the admin view and provides a one-click copy-contact action.
  • Deep-links a submission into proposal creation when it is linked to a contact. The Forms tab also makes the unfinished form-creation connection visible instead of pretending that builder is complete.
See this screen →
5. Unified multi-channel inbox Inbox & Messaging
  • Combines website chat, SMS, and email into one conversation workspace.
  • Merges the legacy chat widget tables with the newer conversations and messages tables so existing history is not stranded in a separate screen.
  • Supports channel filtering, search, unread counts, online/offline widget presence, new conversations, and permission-aware actions.
  • Subscribes to Supabase Realtime changes across the message sources so new conversations and message updates can appear without a full reload.
  • Marks threads read or unread across the relevant underlying records and keeps the visitor's initial enquiry visible even when it was stored outside the normal text table.
  • Each thread carries a Details panel, tag assignment, mark-unread control, and an assistant-generated summary that can be re-analyzed or edited in place.
  • Email replies are sent through the server route. SMS is represented in the UI but remains explicitly not configured until an SMS provider is connected.
See this screen →
6. Chat session controls and human takeover Inbox & Messaging
  • Tracks chat session states such as new, read, replied, and archived, with idle-session indicators based on the configured timeout.
  • Displays captured visitor information such as email, phone, budget, help requested, and appointment details when available.
  • Lets an admin take over from AI, resume AI, pause AI for a conversation, or generate a draft reply for human review.
  • Uses optimistic sending states with failure details and chat retry behavior, while clearly marking a stale visitor session as ended.
  • Supports chat composer shortcuts, a 2,000-character limit, company-field tokens, and per-message detail inspection.
See this screen →
7. Email thread continuity and inbound routing Inbox & Messaging
  • Sends admin email replies through Resend with a subject, message body, provider message ID, and a logged email message in the same thread.
  • Places a conversation-specific reply token in Reply-To so a visitor can answer from a normal mail client without creating a disconnected lead.
  • Validates inbound Resend webhooks with Svix signatures, fetches the received body through the authenticated Resend API, converts HTML-only mail to readable text, and strips quoted history.
  • Resolves replies against chat threads or native conversations, follows merge pointers when a conversation was consolidated, and falls back to a known contact email when no token is available.
  • Handles partial outcomes explicitly: an email can be successfully sent even if logging it back into the thread needs a later refresh, keeping the delivery and logging state visible to the admin.
8. Auth, roles, and permission boundaries Platform & Integrations
  • Protects the private portal with Supabase Auth and rejects unauthenticated or anonymous access at the dashboard boundary.
  • Loads the current user's active role and permission set from a database access function, then uses those permissions in navigation, pages, and API actions.
  • Includes user management, role records, permission definitions, per-user overrides, active/inactive access, and an unauthorized route for blocked users.
  • Keeps ordinary browser requests inside session/RLS-scoped flows. Server-only service-role access is reserved for paths that cannot carry a browser session, such as signed inbound email webhooks and protected automation.
  • Uses database policies and validation alongside UI gates; hiding a button is not the only authorization layer.
See this screen →
9. Multi-assistant AI workspace AI Assistants
  • Smart AI Assist is no longer a single global configuration. It hosts a list of named assistants, each bound to its own channel — an Email Assistant on a dedicated inbound address, and a Website Assistant on the chat widget.
  • Every assistant carries its own model, temperature, token budget, tone, prompts, guardrails, signature, knowledge selection, conversation-intelligence settings, and automation rules. Changing one never changes the other.
  • The assistant list shows active state, automation posture (Sends automatically versus Approval required), the selected model, whether Conversation Intelligence is on, and when the assistant was last updated.
  • Each row offers a direct Test action and an Open assistant route into the five configuration tabs: Basic Setup, Advanced Setup, Automation, Conversation Intelligence, and Knowledge Sources.
  • Renaming an assistant is deliberately an internal-label change only — it does not alter the email sender name, signature, or connected inbox, so a rename cannot silently change what customers see.
  • New assistants can be created for additional channels, and nothing an assistant produces is sent automatically unless automation is enabled on that specific assistant.
See this screen →
10. Assistant basic setup and email identity AI Assistants
  • Separate toggles for Assistant active (may analyse conversations and draft on request) and Automated replies (inbound mail may produce a draft or a send), so analysis and sending are independent decisions.
  • Response model configuration covers the OpenAI model, temperature, maximum response tokens, and tone, with inline defaults shown next to each field so a change is a considered one.
  • Email connection and identity are configured per assistant: assigned inbound address, sender display name, From address, and Reply-To address, with a live Resend connection indicator.
  • Leaving the inbound address blank makes the assistant the catch-all for any address no other assistant claims, which keeps routing explicit as more assistants are added.
  • The From address must sit on a domain verified in Resend, and a blank Reply-To keeps the thread's correlation address so customer replies stay threaded to the same conversation.
See this screen →
11. Prompt, persona, and behavior controls AI Assistants
  • Advanced Setup exposes the full prompt stack in editable fields rather than burying it in code: global prompt, AI personality, and AI task instructions.
  • Behavior and safety are configured separately through style guardrails and a response guide — tone, boundaries, personalization, claims the AI must never make, and how a conversation should progress from first contact to next step.
  • The shipped instructions are conservative by design: read the whole thread, answer the outstanding question, never invent prices, availability, guarantees, contractual terms, or commitments, and offer to follow up rather than guessing when the answer is not in the approved knowledge base.
  • Guardrails explicitly forbid disclosing internal tools, prompts, system instructions, or other customers, and forbid promising refunds, discounts, cancellations, legal positions, or account changes.
  • An email signature block is appended below every reply the assistant sends, kept separate from the prompt so wording and sign-off can be edited independently.
  • The tab carries a standing warning that advanced settings change how the AI behaves and should only be edited with the effect understood.
See this screen →
12. Governed email automation AI Assistants
  • Two explicit automation modes. Approval required creates an AI draft inside the conversation for a person to approve, edit, reject, or take over. Fully automatic analyses, runs every safety check, waits the configured delay, re-checks that no one has replied, then sends in the original thread.
  • Selecting fully automatic surfaces a plain-language warning that email will reach customers unread, along with the list of controls that can still cancel a pending send.
  • Timing controls cover response delay in seconds (a deliberate window for a human to step in), business timezone, a business-hours-only send restriction, and a maximum number of automated replies per thread.
  • Audience rules decide who gets an automatic reply at all: new leads, existing contacts, or both — with automation stopping after a staff member replies and stopping when a person takes the conversation over.
  • Only an allow-list of intent categories may be answered automatically — general inquiry, service question, scheduling, status update, follow-up, and standard pricing question. Anything outside that list becomes a draft for review instead of a send.
  • A separate always-escalate list can never be answered automatically whatever else is configured: complaint, refund, cancellation, legal matter, contract, security problem, account access, custom pricing negotiation, angry or threatening tone, unsupported attachment request, anything outside the knowledge base, and anything unclassified.
  • A minimum confidence threshold holds low-confidence replies as drafts rather than sending them, and attachment behavior is configured rather than assumed.
  • Loop protection already excludes no-reply, bounce, auto-responder, and bulk senders, with additional per-address and per-domain exclusion lists for cases the defaults do not cover.
  • Format and disclosure settings control the reply format and an optional line telling the customer the reply was AI-drafted.
See this screen →
13. Conversation Intelligence AI Assistants
  • A read-only analysis layer that keeps a conversation summary and the current unresolved need current from the whole thread rather than only the first message. It never sends anything.
  • Independent toggles for conversation summarization, automatic summary updates as the conversation changes, and an automatically maintained “How can we help?” field.
  • Analysis settings use a separate, cheaper extraction model from the reply model, plus an analysis delay that waits for a burst of messages to settle so a fast back-and-forth is analysed once instead of on every message.
  • A confidence threshold flags weak analysis for review rather than treating it as trusted, and free-text summary instructions steer what the summary should emphasise.
  • Re-analysis, manual editing, and field locking live on each conversation in Messages, so a summary is corrected where it is actually read — and a locked field is not overwritten by the next automatic update.
See this screen →
14. AI draft review inside Messages AI Assistants
  • Drafts appear inline in the conversation they belong to, labelled with the owning assistant, an AI DRAFT badge, the reply subject, and the time the draft was produced.
  • Review actions are explicit and separate: approve and send, edit before sending, edit the draft text, regenerate, or reject.
  • Each draft displays the classified category, the confidence score, and its origin — whether the assistant produced it automatically or a staff member requested it — so the reviewer can see why this draft exists.
  • A Draft a reply with AI action lets an admin request a draft on demand for any thread, and drafts can be hidden or expanded without leaving the inbox.
  • The manual composer stays available alongside the draft panel, so using AI is never a prerequisite for answering.
See this screen →
15. Knowledge base, per-assistant sources, and portfolio crawl AI Assistants
  • Adds manual documents, policy or FAQ-style content, approved wording, and optional reference links to the AI context.
  • Allows sources to be searched, previewed, paginated, activated or paused, and deleted without changing the underlying application prompt.
  • Includes a crawler route that walks the portfolio's approved same-origin HTML pages in bounded batches, excludes static assets, and separates crawler-owned sources from manual sources — so each published case study becomes a citable source.
  • Documents live once in a shared Knowledge Base; each assistant then chooses which of them it may quote. Selecting nothing means the assistant reads every active source, including ones added later.
  • A source assignment affects only the assistant being edited — other assistants keep their own selection, so a narrowly-scoped email assistant and a broad website assistant can share one library without interfering.
  • Shows source counts and active/paused state so the admin can see exactly what each assistant is allowed to use.
See this screen →
16. Customer-facing chat auto-reply path AI Assistants
  • Separates the human-reviewed draft workflow from the optional customer-facing chat auto-reply Edge Function.
  • Requires a valid visitor session, verifies that the visitor owns the conversation, and checks both the AI enabled flag and the auto-reply enabled flag before responding.
  • Uses idempotency events, durable reply-turn leases, quiet/max-wait batching, conversation rate limits, visitor rate limits, and Realtime typing signals to control automated behavior.
  • Loads approved knowledge, memories, and recent history; retries provider calls; falls back to configured text; and records successful or failed turns.
  • Can extract contact and scheduling details from the conversation and update the connected lead records, while keeping booking confirmation separate from an AI guess.
  • Admin takeover pauses the automated path. The switch is off by default and can be enabled deliberately from the assistant's Automation tab after the provider configuration is ready.
See this screen →
17. AI-assisted proposals Documents & Billing
  • Lists proposals with totals by draft, sent, and accepted status, plus contact and service context.
  • Starts a proposal from a selected contact, multiple active services, optional notes, or a form submission deep-link that preselects relevant service names where possible.
  • Generates structured proposal data for title, summary, scope, timeline, next steps, pricing line items, totals, and notes, then sanitizes and validates the response before saving.
  • Supports regeneration from saved contact and service context, with an overwrite warning when unsaved edits exist.
  • Lets the admin edit title, status, overview, scope, timeline, next steps, pricing rows, totals, and pricing notes before saving.
  • Exports a branded PDF through a server-side React PDF template and surfaces partial-save warnings when a main proposal row succeeds but related links need attention.
See this screen →
18. Shared services catalog Documents & Billing
  • Maintains one shared catalog for both proposals and invoices so the two document workflows do not drift into parallel service definitions.
  • Stores service name, description, base price or custom-quote mode, default discount, and active/inactive state.
  • Supports catalog search, add, edit, activation toggles, and safe deletion behavior.
  • Prevents deletion when a service is already referenced by a proposal or invoice; inactive is the safe alternative for historical items.
19. Invoicing and financial document output Documents & Billing
  • Provides invoice and catalog tabs, invoice-number search, client/company search, status filters, and a dedicated new-invoice flow.
  • Builds invoices for an existing or new client, using contact deduplication when a new client is entered.
  • Supports issue and due dates, tax rate, notes, catalog lines, custom lines, quantity, unit price, and per-line discount percentages.
  • Calculates line totals, subtotal, discount total, tax, and grand total in the UI, then recomputes the financial values server-side before persistence.
  • Supports draft, sent, paid, and overdue states, editable detail records, and downloadable invoice PDFs.
  • Returns partial warnings where appropriate so a client-facing invoice is not silently reported as fully saved when related line-item work failed.
See this screen →
20. Appointments, recurrence, and timezone-aware scheduling Scheduling
  • Offers month, week, and list calendar views with navigation, search, sorting, upcoming appointments, and past-due attention.
  • Creates and edits appointments for an existing contact or new contact details, with title, location, notes, start/end, status, category, and assigned admin.
  • Separates the admin's display timezone from the contact-facing client timezone so a requested call means the same thing to both sides.
  • Supports recurring rules, expanded occurrences, per-occurrence overrides, rescheduling, and recurrence-aware updates.
  • Checks conflict candidates in the UI and protects blocking appointments with database-level overlap rules; completed and canceled items do not block a new slot.
  • Supports complete, cancel, delete, and manual sync actions with clear status treatments in the calendar views.
See this screen →
21. Google Calendar integration Scheduling
  • Connects and disconnects an admin's Google Calendar through OAuth with stored connection metadata and token refresh handling.
  • Pushes eligible local appointments and recurring series to Google, updates or deletes events as local records change, and invites the contact when an email is available.
  • Includes a bounded sync-all action for today's and upcoming appointments that have not been synchronized.
  • Provides a Pull from Google action for edits made in Google Calendar, including reschedules, cancellations, title changes, and notes.
  • The current implementation provides local-to-Google push plus manual inbound pull; automatic provider push notifications are intentionally a later phase.
22. Job-hunt discovery and human-submitted applications Platform & Integrations
  • Maintains an outbound-work profile with skills, preferred project types, minimum rate, keywords, bio, match threshold, and portfolio case studies.
  • Discovers listings from configured Upwork and Freelancer connectors, supports manual job entry, and reports when a provider is not connected rather than pretending that zero results means failure.
  • Filters by source, open/applied/skipped state, minimum match score, and time window; strong matches, draft-ready listings, and applied work are summarized.
  • Scores listings against the saved profile, drafts a cover letter or proposal, and lets the user edit, regenerate, save, copy, or mark the listing applied or skipped.
  • Realtime listing updates and job-match notifications keep new opportunities visible.
  • The pipeline deliberately stops before submission: the user reviews, copies, and sends the application. Scheduled discovery can run independently, while human approval remains required before an application is sent.
See this screen →
23. Integrations and connected services Platform & Integrations
  • Settings is organized into General, Integrations, and User Management, with Integrations acting as one place to see every external service the CRM depends on.
  • Providers are grouped by what they do — AI, Communication, Productivity, and Payments — instead of being listed as an undifferentiated pile of API keys.
  • Each provider card carries an explicit connection state. OpenAI, Resend, and Google Calendar report as connected; Telnyx (SMS and calling) and Stripe (payments and billing) report as not configured rather than appearing to work.
  • Cards expose the actions that make sense for their state: manage, a deep link into the workspace that consumes the provider, and reconnect or disconnect where a user-level OAuth grant exists.
  • Server-configured credentials are shown as configured on the server rather than being surfaced in the browser, and the Google card reports which calendar the synchronization is bound to.
  • Unbuilt integrations are labelled coming soon on purpose — the screen is meant to be an honest map of what the system can actually reach today.
See this screen →
24. Notifications and company settings Platform & Integrations
  • Aggregates new form submissions, visitor chats, inbound messages, project comments, and job matches in a notification center with realtime updates and deep links.
  • Supports mark-read behavior and routes each notification to the relevant CRM surface.
  • Configures notification recipients, master notification state, autoresponder behavior, reply alerts, and test-email delivery.
  • Stores company name, contact details, website, and address for consistent operational and email context.
  • Company fields can be inserted into outbound composer content, while shared email templates keep admin notifications and visitor-facing messages visually consistent.
See this screen →
25. Supabase data layer and operational safeguards Platform & Integrations
  • Uses Supabase Auth, Postgres, Row Level Security, Realtime subscriptions, database functions, constraints, and migrations as one application boundary.
  • Keeps legacy widget data, native conversation data, contacts, forms, proposals, services, invoices, appointments, AI records, notifications, and job listings connected through explicit identifiers and merge behavior.
  • Validates bounded inputs and provider requests in server routes, uses structured AI output for document generation, and preserves meaningful partial-failure messages for integrations.
  • Separates browser session flows from server-only keys and signed webhook paths; secrets are read from server configuration rather than exposed in the client.
  • Realtime listeners, idempotency records, rate limits, leases, conflict constraints, soft deletion, and explicit retry states are used to keep asynchronous workflows understandable.
Technical Implementation

A protected Next.js application over a connected Supabase data layer.

The product is implemented as a typed App Router application with server and client responsibilities kept explicit. The browser handles interactive workspace state, while authenticated route handlers, database policies, and server-only integration paths protect the data and provider boundaries.

Frontend

Next.js App RouterReactTypeScriptTailwind CSS

Data & auth

Supabase AuthPostgresRow Level SecurityRealtimeEdge FunctionsMigrations

Integrations

OpenAIResendGoogle CalendarUpwork & FreelancerTelnyx (planned)Stripe (planned)

Documents & safety

@react-pdf/rendererStructured JSON outputSvix-signed webhooksServer-only secrets
11 technical layers
1. Application architecture
  • Built with Next.js App Router, React, TypeScript, and Tailwind CSS, with route groups separating the protected dashboard shell from public login and unauthorized states.
  • Server-rendered dashboard layouts load the authenticated user, access context, notification inputs, and initial workspace data before passing it into focused client components.
  • Interactive modules use dedicated hooks and components for conversations, messages, calendar views, AI state, permissions, notifications, proposals, invoices, and job listings rather than one monolithic page.
  • The navigation highlights nested detail routes and filters entries through the current permission set, so the interface and URL structure stay aligned as the product grows.
2. Authentication and authorization flow
  • Supabase SSR clients read the authenticated cookie session on the server, while the protected dashboard layout redirects unauthenticated users to login and unauthorized users to a dedicated access-denied route.
  • Access is resolved through the database-backed current_user_access function into an active role, system-role flags, and granular permission keys.
  • Page components hide unavailable navigation and actions, while API handlers repeat authentication and permission checks before mutating records.
  • Browser requests use session-scoped Supabase access and RLS. Service-role clients are isolated to server-only paths that need to operate without a browser session, including signed inbound webhooks and the protected auto-reply function.
3. Supabase schema and data relationships
  • Supabase Postgres stores contacts, submissions, tags, conversations, messages, appointments, proposals, services, invoices, AI settings and history, notifications, and job-hunt records.
  • Migration-backed constraints and RPCs handle normalized contact matching, soft-delete restoration, tag association, invoice numbering, proposal/service links, appointment overlap rules, recurrence, merge pointers, and permission resolution.
  • The shared services table is intentionally referenced by both proposals and invoices, avoiding two competing catalogs for the same business offering.
  • Foreign keys, unique indexes, RLS policies, status fields, and explicit timestamps preserve ownership and lifecycle behavior across the connected modules.
4. Messaging normalization and realtime updates
  • The inbox reads legacy chat_messages/chat_texts widget records alongside native conversations/messages records and normalizes them into one rendered thread model.
  • Conversation and message hooks subscribe to Supabase Realtime inserts and updates, then reconcile unread state, channel badges, typing indicators, status changes, and selected-thread content.
  • Conversation merges retain durable identifiers and reply tokens, allowing historical email replies to follow the survivor rather than disappear into a hidden row.
  • Optimistic message sending, explicit failed states, chat retry behavior, idle-session handling, and message-detail metadata make asynchronous behavior visible to the admin.
5. Server routes and integration boundaries
  • Next.js route handlers cover AI chat and crawling, contact import, appointments and recurrence actions, Google Calendar OAuth and synchronization, proposal generation and PDFs, invoice creation and PDFs, email delivery, notifications, and job discovery.
  • Request handlers validate authenticated users, normalize inputs, bound payload sizes and timeouts, recompute financial totals server-side, and return meaningful 4xx/5xx or partial-save responses.
  • Resend outbound mail is centralized in shared email helpers, while inbound mail is verified through signed webhook headers before the service-role path reads and files the message.
  • Google Calendar, OpenAI, Upwork, Freelancer, Resend, and optional scheduled discovery are isolated behind explicit provider modules and environment configuration instead of being mixed into presentation components.
6. Multi-assistant configuration model
  • Assistant configuration is stored per assistant rather than as one global AI settings row, so model, prompts, guardrails, signature, automation rules, intelligence settings, and knowledge selection are all scoped to a single assistant record.
  • Each assistant declares the channel it serves and the routing key for that channel — an inbound email address for the Email Assistant, a widget identifier for the Website Assistant — which is what allows one inbound message to resolve to exactly one owning assistant.
  • An unclaimed inbound address falls through to the assistant configured as the catch-all, keeping routing deterministic as assistants are added or removed.
  • Knowledge assignment is a join between an assistant and the shared knowledge sources, with an empty selection deliberately meaning “every active source” so newly added documents are available without revisiting each assistant.
  • The internal assistant name is kept distinct from the customer-facing sender identity, so display name, From address, Reply-To, and signature are never side effects of a rename.
7. Email automation pipeline and safety gates
  • An inbound email that passes signature verification is filed into its thread, matched to its owning assistant, and only then evaluated for automation — delivery, filing, and automation are separate stages with separate failure modes.
  • Classification runs before generation. The resulting intent category is checked against the assistant's allow-list and its always-escalate list, and the returned confidence is compared to the configured threshold before any send is considered.
  • Loop protection screens no-reply, bounce, auto-responder, and bulk senders, with configured address and domain exclusions applied on top.
  • Fully automatic sends observe the configured delay, business-hours window, business timezone, and per-thread reply cap, then re-check immediately before sending that no staff member has replied and no one has taken the conversation over.
  • Any gate that fails downgrades the outcome to a stored draft rather than dropping the message — a blocked send always leaves a reviewable artifact in the conversation with its category, confidence, and origin recorded.
  • Drafts and sends are written into the same thread model the inbox already renders, so an approved draft becomes an ordinary outbound email with the correct subject and threading headers.
8. Conversation intelligence extraction
  • Summarization runs on a separate extraction model from the reply model, keeping the analysis path cheaper and independent of reply quality tuning.
  • A debounce window waits for a burst of messages to settle before analysing, so a rapid exchange produces one analysis pass rather than one per message.
  • Extracted fields are stored on the conversation with their confidence, and results below the threshold are flagged for review instead of being written as trusted values.
  • Manually edited and locked fields are respected by subsequent automatic updates, so a human correction is not overwritten by the next analysis run.
  • The intelligence path is strictly read-only with respect to the customer — it never sends, and it is enabled independently of any reply automation.
9. AI execution and document generation
  • Assistants build bounded context through dedicated context and prompt modules, combining approved knowledge, memories, recent enquiries, recent chats, comments, and the active conversation.
  • Saved per-assistant settings control model selection, tone, temperature, token budget, timeouts, response length, batching, persona, guardrails, signature, and fallback behavior.
  • Proposal generation requests structured JSON, validates and sanitizes the result, and persists editable proposal and line-item records instead of treating raw model text as a final document.
  • Proposal and invoice PDFs are rendered through server-side @react-pdf/renderer templates, keeping document layout separate from browser-only controls.
  • Customer chat auto-reply is a separate Edge Function with server-only secrets, visitor ownership checks, rate limits, idempotency events, durable turn claims, typing signals, and an admin pause path.
10. Integration surface and provider state
  • Each provider is represented by an explicit connection state derived from real configuration — a server-held credential, a stored OAuth grant, or nothing at all — rather than from a hardcoded label.
  • Server-configured providers such as OpenAI and Resend report as configured on the server without exposing key material to the browser; user-granted providers such as Google Calendar expose reconnect and disconnect because the grant belongs to a user.
  • Unconfigured providers render a not-configured state with their capability described, so an operator can tell the difference between “broken” and “never set up”.
  • Provider modules stay isolated behind their own interfaces, which is what makes adding an SMS or payments provider a configuration and adapter change rather than a rewrite of the surfaces that would consume it.
11. Reliability, safety, and completion boundaries
  • Provider calls use bounded timeouts, retries where appropriate, smart truncation, fallback text, and explicit error details so a successful HTTP response is not mistaken for a completed business operation.
  • Automation is opt-in and reversible per assistant: approval-required is the default posture, fully automatic must be selected deliberately against a stated warning, and category, confidence, staff-reply, and takeover rules can all cancel a pending send.
  • Human control is preserved at each layer: assistants draft suggestions, the job-hunt pipeline stops before application submission, and chat auto-reply has an independent off switch and takeover control.
  • Secrets such as OpenAI, Resend, OAuth, cron, and service-role credentials remain server-side; the case study does not expose or claim any secret value.
  • The deployment is active; ongoing completion work is focused on controlled changes, reliability, monitoring, and continued validation as the live product evolves.
System Surfaces

Designed as one product, not a pile of disconnected pages.

The CRM's value is in the handoffs: the same contact can appear in a form review, a multi-channel thread, an AI draft awaiting approval, a proposal, an invoice, a calendar appointment, and a notification without losing the surrounding context.

The build is intentionally private and operational. It is a workspace for making the next decision visible: reply, approve, review, schedule, propose, invoice, or follow up — with automation that has to earn each send rather than assume it.
Build Status

In progress — this project is not marked finished.

The feature surface is broad and the current source contains the working routes, components, APIs, migrations, and integration paths described above. The product is already live and remains an active build while its final iteration, safe screenshots, reliability work, and ongoing improvements continue.

Implemented in source

Authenticated CRM navigation, contact and conversation workflows, the multi-assistant AI architecture with per-channel email automation and conversation intelligence, the in-inbox draft approval flow, the Integrations surface, proposal and invoice documents, scheduling, notifications, job discovery, and the supporting Supabase data model are represented in the current checkout.

Still being completed

Active product work includes the Telnyx SMS/calling and Stripe payment integrations that are currently marked not configured, continued permission and company-settings refinement, redacted visual documentation, reliability improvements, and the polish needed as the live system evolves.

Live system

The CRM is live in its current production environment and connected to the portfolio operations workflow. The case study keeps private credentials and internal deployment details out of the public page while documenting the active implementation and its deliberate product boundaries.

Comments

Have thoughts on this project?

Leave a public comment — questions, feedback, or your own experience with a similar system.

Loading comments…

Need one workspace for the work between your tools?

I build private CRM and operations systems that keep lead context, automation, and human decisions connected.

Work With Me →