CrawlSpace CRM

Help Library

Tab-by-tab how-tos for operating and configuring CrawlSpace CRM. Looking for quick answers instead? Try the FAQ.

Dashboard

The Dashboard is your morning landing pad. It rolls up the team's current pipeline, the work due today, and how every active sequence is performing. Most actions on the Dashboard are drill-downs -- click any card or stat to open the underlying tab pre-filtered to that slice.

What every widget shows

  • Stat cards -- Companies, Leads, Emails Sent, Tasks Due, Pipeline Value, Active Customers. The Emails Sent and Pipeline cards respect the date-range picker and scope (Mine / Team).
  • Weekly Performance + Engagement bubbles -- compact view of opens, clicks, replies, and calls plotted across the last 7 days.
  • Play bar -- a single-row timeline of the day's outbound activity so you can see at a glance whether the team has been busy.
  • Due Tasks (next 6) -- next-up tasks sorted by Priority (overdue first), Due date, or Engagement (warm-lead-first ranking).
  • Upcoming Meetings -- the next few items on your connected calendar.
  • Activity Feed -- unified call / email / system / meeting / note / sequence events with one-click filter pills.
  • Emails Sent analytics + Best Time to Call heatmap -- both honor the date-range picker.
  • Monthly Goals, Sequence Performance, Deliverability (30d), Call Activity (30d) -- per-cadence and per-deliverability rollups.
  • Conversion Funnel + Lead Sources -- where leads enter the pipeline and where they fall out.
  • Team Leaderboard + Your Performance -- per-rep totals so reps can see how they're tracking.

Filters that change what you see

  • Scope picker (top of the dashboard) -- "Mine" filters every contact-derived widget to contacts you own; "Team" widens to the whole org.
  • Date range picker -- changes the window the Emails Sent / Play bar / heatmap / aggregations compute against. Persists per-session.
  • Activity Feed pills -- toggle individual event types in the feed without affecting other widgets.

Setup checklist card

On a fresh account, a Setup Checklist card appears at the top of the dashboard listing the remaining onboarding steps (connect a sheet, connect email, invite a teammate, etc). The card hides itself when every item is done OR when you dismiss it with the "x". You can bring it back from Settings -- Workspace.

Common questions

Why is the Emails Sent count different from what I see in Newsletters? The Dashboard counts every outbound email (sequences, drips, one-offs, newsletters). The Newsletters tab only counts blast newsletters. The Activity Log shows every individual send.
I see "0 pipeline" but I have open deals. The Pipeline card respects the Scope picker. If you're on "Mine" and the deals belong to a teammate, switch to "Team".
Open the Dashboard

Leads

The Leads tab is your sales pipeline -- every contact you haven't sold yet lives here. A lead carries a sales-cycle status (New -> Working -> Connected -> Contract -> Closed Won/Lost) plus owner, source, and the tags / custom fields you map from your sheet. Closed-Won leads automatically convert to customers.

Creating leads

  • Manual -- "+ Add Lead" button at the top of the tab.
  • CSV import -- upload your existing list. The mapper auto-detects common column headers; required fields are flagged with a red banner you can't dismiss until they're mapped. Within-CSV and against-CRM dedupe both run pre-import so you can resolve duplicates before they land.
  • Lead Search -- built-in prospect crawler. Pull leads from a list of source URLs or run a Prospect Finder query; matches against your existing CRM are flagged at import time so you can skip, update, or merge before they land.
  • Webhooks -- Facebook Lead Ads + LinkedIn Lead Gen flow into the Leads tab automatically once you connect them in Settings -- Integrations.

The status lifecycle

Statuses are not free-text -- they're a controlled list that drives funnel reports + the auto-convert hook. The default states for leads are:

  • New -- haven't touched yet
  • Working -- in active outreach
  • Connected -- had a live conversation
  • Contract -- proposal / quote out
  • Closed Won -- auto-converts to customer
  • Closed Lost / Not Interested / Dead -- removed from active pipeline; surface in reports
A status sub-reason field appears next to the status whenever the chosen status supports it (e.g. Not Interested -> Price, Timing, Unresponsive). Use it -- the Lead Loss report relies on it.

Filtering, search, and saved views

  • The filter bar above the list combines status, source, owner, tag, company, and any custom field. Toggle Include vs Exclude per filter to write "everyone except churned" queries.
  • Saved filters live in your browser (per-device) so the same query you build today is one click away tomorrow.
  • Search is full-text across name, email, company, phone, and title.

Bulk actions

Multi-select with the row checkboxes (or "select all visible") then use the sticky action bar to reassign owner, apply a tag, enroll into a sequence, or delete. Bulk owner reassignment respects org permissions -- non-admins can only reassign leads they currently own.

Duplicate detection & merge

The CRM watches your contact list for duplicates and surfaces them in a small Duplicates pill in the sidebar with a count badge. It looks at three signals at once: matching email (case doesn't matter), matching phone (any of the three phone fields, ignoring formatting so "(555) 123-4567" and "5551234567" still match), and matching name + company (exact or close fuzzy match). This catches duplicates that pure email matching would miss — the same person entered twice from different sources.

  • Sidebar pill, not a pop-up -- duplicates surface unobtrusively. Click the pill to open the resolver. The count refreshes whenever you add, edit, delete, merge, or import contacts.
  • Checked when you add a new contact -- if a possible match exists, you get an "Open existing / Save anyway / Cancel" dialog instead of silently creating the dupe.
  • Checked at CSV import + Lead Finder -- the importer flags incoming rows that already match an existing contact and lets you skip, update, or import anyway.

The merge picker

Clicking Merge on a pair opens a side-by-side picker. Every field with a value on either side appears as two radio options. Fields where the values differ are highlighted yellow so you can spot them at a glance.

  • Fields covered: name, first/last name, company, title, primary phone, all phones, website, street/city/state/zip individually (so address pieces from the contact you're removing aren't silently dropped), notes, sequence + step + due date, status + status reason, amount, LinkedIn, Facebook, and Created Date.
  • The "Keep All from Contact 1 / 2" buttons at the top let you bulk-select one side. You can still override individual fields after.
  • For the All Phones row your radio choice is honored — pick Contact 1 to keep only Contact 1's phones, or Contact 2 for theirs. Earlier versions ignored your pick and combined both; that quietly violated what the buttons promised.

What happens to linked records on merge

  • Activity logs — combined from both contacts, re-sorted by date, attached to the contact you kept.
  • Deals — reassigned to the kept contact automatically.
  • Calls, scheduled emails, voicemails, recordings, invoices, subscriptions — cleaned up by a single behind-the-scenes sweep so nothing is left pointing at the contact you removed.
  • If the cleanup sweep can't run for any reason, the merge surfaces a warning telling you exactly what might still reference the removed contact — you'll never get a silent partial merge.
"This merge is permanent" The red banner in every merge dialog is the truth: there is no undo. Review the field picks carefully before clicking Merge Contacts. If a pair isn't actually a duplicate, click "Not a duplicate" instead — the system remembers the pair and won't surface it again.

Common confusion points

"I converted a lead but it still shows in Leads" -- Conversion sets recordType = 'contact'. If the row didn't move tabs, the status change didn't fire the auto-convert (e.g. you set it directly in the sheet instead of via the CRM). Open the contact and re-pick "Closed Won" -- the convert runs.
"My CSV imported but every row is missing the email" -- Column mapping is per-column, not per-field. Re-open the importer, verify the email column is mapped to Email, then re-import the missing rows (or use the duplicate resolver to merge).
Open the Leads tab

Customers

The Customers tab is everything after the sale: ongoing accounts you're nurturing, renewing, or growing. A customer is a contact with recordType = 'contact' and a relationship status (Sold, Onboarding, Active / Renewed, At Risk, Churned). They're the same database row as a lead -- just on the post-sale side of the lifecycle.

How a lead becomes a customer

Two paths:

  • Auto-conversion -- moving a deal to a Won stage flips the linked lead's record type to customer and stamps the contract start date.
  • Manual -- on a lead row, set status to Closed Won and the convert hook fires.

Customer-only fields

  • Contract start / renewal date -- drives renewal reminders.
  • Check-in cadence -- Weekly / Monthly / Quarterly. Surfaces overdue check-ins on the Dashboard + Tasks.
  • Last check-in + next check-in -- auto-computed from cadence and last_contacted timestamp; you can override.
  • Health score -- computed from engagement, override-able with a manual score that always wins.
  • Revenue timeline -- per-customer history of contract value changes.

Filtering + saved views

The customer filter bar is denser than the Leads bar because relationship segments matter more here. Filter by status, cadence, city/state, owner, tag, segment, or saved filter. Saved customer filters persist per-device.

Common confusion points

"A customer disappeared from the tab" -- Most likely their status flipped to Churned. The default Customers view hides churned accounts; toggle the Churned chip to bring them back.
"Health score never updates" -- A manual health score override blocks the computed score. Clear the override in the customer detail card to let the auto-score recompute.
Open the Customers tab

Companies

The Companies tab is an account-level rollup of every contact that shares the same company string. Companies are derived, not stored as their own records -- which keeps the model simple but has a few sharp edges to watch for.

How grouping works

  • Every contact carries a company text field. Companies tab groups by exact case-insensitive match on that string.
  • "Acme Corp" and "acme corp" are the same group; "Acme Corp." (trailing period) is a different group.
  • Empty company strings are skipped (you don't get an "Unknown" bucket).

What you see per company

  • Roster -- every contact at the company, with their status, last-contact date, and deal value.
  • Pipeline rollup -- total open deal value at the account.
  • Activity stream -- merged activity across every contact at the company.
  • Coverage -- which rep owns which contact (useful when multiple reps split an account).

What the Companies tab can't do

Companies are derived, not first-class records. That means you can't:
  • Add custom fields to a company (custom fields live on the contact)
  • Rename a company in one click (rename it on every contact, or use bulk edit)
  • Attach a deal directly to a company without a contact

Common confusion points

"Two companies look identical but show separately" -- Look for trailing whitespace, punctuation, or "Inc" / "LLC" variations. Open both groups, copy the canonical spelling, and use bulk edit on the contacts in the wrong group.
"I deleted a contact and the company disappeared" -- Expected. With zero contacts at a company string, the group has nothing to render.
Open the Companies tab

Deals & Pipeline

A deal is a specific opportunity: one contact, one value, one expected close date, one stage in your pipeline. Deals live in their own tab with a Kanban board (drag between stages) and a sortable list view. Every deal is 1:1 linked to a contact and optionally 1:1 linked to an invoice + a project.

Creating a deal

  • From the Deals tab -- "+ New Deal" opens a modal. The contact picker is required; you can't create an unattached deal.
  • From a contact -- the contact detail panel has a "+ Deal" shortcut that pre-fills the link.
  • From a Quote -- accepting a quote auto-creates a deal pre-filled with the quote total and the contact.
  • From a status change -- moving a lead's status to a configured "convert" status can auto-create a deal (toggle in Settings -- Workspace).

The stage lifecycle

Stages are org-configured (Settings -- Team -- Pipeline Stages if you're an admin). Each stage has:

  • Key + Name + Color -- the column on Kanban
  • Probability default -- auto-applied when a deal lands in this stage (you can still override per-deal)
  • Won flag -- moving here triggers the lead-to-customer conversion (if enabled) and bumps probability to 100
  • Lost flag -- requires a lost_reason to save; bumps probability to 0
Moving a deal to a Lost stage with no reason is now rejected by the server -- the Kanban prompts you for one. This keeps the Lost-by-Reason report meaningful.

Kanban vs List view

  • Kanban -- one column per stage, drag-and-drop cards. Each card shows title, contact, amount, days-in-stage badge, and the deal's owner avatar.
  • List -- sortable table with every field visible. Better for bulk-edit and CSV export.

The view toggle is at the top-right. Your last-used view persists per device.

Forecasting + weighted pipeline

The Dashboard's Pipeline Value stat sums amount_cents x (probability / 100) for every open deal (deals not in a Won or Lost stage). Probability auto-syncs to the stage's default whenever you change the stage -- so "Closed Won" deals always count at 100% and "Discovery" stays at whatever the admin set as the default.

Activities + notes

Every deal carries an internal activity log: created, stage changes, field changes, won/lost, free-form notes. Add a note from the deal modal's inline input -- it saves immediately, no need to click the global Save. The activity log is capped at the most recent 500 entries per deal; older entries are trimmed when a 501st is appended.

Every deal activity also mirrors into the unified Activity Log so the team feed surfaces deal moves alongside calls / emails / meetings.

Linking

  • Contact (required, 1:1) -- moves with the deal across stages.
  • Invoice (optional, 1:1) -- bidirectional. Linking from either side stamps the FK on the other.
  • Project (optional, 1:1) -- attach to track delivery once the deal is won.

Permissions

Only the deal's owner or an org admin/owner can move it between stages. Non-owners see the cards but can't drag them (and a direct API call gets a 403). Reassign ownership from the deal modal -- admins can hand a deal off to any member.

Common confusion points

"Why does my pipeline number look low?" Weighted pipeline = amount x probability. A $50k deal at 20% counts as $10k. The unweighted total is in the dashboard drilldown.
"I closed a deal but the lead is still in Leads" The Won -> Customer conversion is gated by an org setting (Settings -- Workspace -- Lead Conversion). If it's off, deals won't move the contact automatically.
"Days-in-stage badge is wrong" The badge counts from stage_changed_at, which only updates on stage changes. Editing other deal fields doesn't reset the counter.
Open the Deals tab

Lead Finder

Lead Finder is the prospecting tool built into the CRM -- it's the feature that gave CrawlSpace CRM its name. Two modes work together: Website Crawl pulls contact info out of a specific company's website, and Prospect Finder searches business directories to discover companies you don't have yet. Results are checked for duplicates against your existing CRM at import time so you decide per-row whether to skip, update, or create new before they land in the Leads tab.

When to use each mode

  • Website Crawl -- "I know the company; I want their contacts." Point it at a domain (e.g. acmecorp.com) and it walks the site looking for names, emails, phones, titles, and addresses on contact / about / team pages.
  • Prospect Finder -- "I want plumbers in Austin, TX." Searches business directories by industry + location and returns a deduplicated list of companies you can import.

Crawl depth

Website Crawl asks you how deep to scan: just the homepage, the homepage + 20 pages, all the way up to 500 pages. Deeper crawls catch contact info hidden in team / about / blog pages but take longer and produce more low-quality hits.

Start at Homepage + 20. If you don't find what you need, rerun at Homepage + 100. Going past 200 only pays off for very large company sites with many landing pages.

The credit system

Website Crawl is free and unlimited. Prospect Finder uses search credits because directory APIs cost money when Google Places is enabled. The rules:

  • 10 free credits per month, rolling reset (not calendar month).
  • $0.40 per credit after the free allowance, up to 30/month total.
  • Cache hits are free -- if anyone on your team ran the same query in the last 24 hours, you get the same results with zero credits. The "served from cache" badge tells you when this happens.
  • Refund on failure -- if a paid search debits a credit but the directory throws before returning results, you get the credit back automatically (with a transaction-log entry).

What counts as a "paid" search

Only Prospect Finder runs that include the Google Maps directory consume credits. DuckDuckGo, BBB, Yellow Pages, and Manta are all free directories -- a Prospect Finder run with Google unchecked is zero-credit.

Result quality + deduplication

  • Cross-directory dedup -- if "Acme Plumbing" appears in Google + BBB + DuckDuckGo, you see one row, not three. Matching is by normalized website domain first, then fuzzy name + address.
  • CRM dedup -- before any row imports, the CRM checks the existing contact list and flags duplicates so you can skip / update / merge instead of creating doubles.
  • Email quality tiers -- personal emails (sarah@acmecorp.com) score higher than generics (info@acmecorp.com, contact@..., support@...). Both import, but the quality tier shows on the row so you know what you're getting.

Saved searches + history

Star a search to save it (give it a name -- "Atlanta plumbers, weekly"). Saved searches re-run with one click. The "Force fresh" toggle bypasses the 24-hour cache when you want to see what's new since the last run.

Lead source tagging

Every imported lead gets a lead_source stamp. Set it once at the top of the Lead Finder UI ("CrawlSpace - Prospecting", "Austin plumber blitz", whatever you want) and every row from that search inherits it. The Source Performance report uses these tags to compare conversion rates by channel.

Common confusion points

"Why did I get charged 0 credits but the result count is high?" A teammate ran the same query in the last 24 hours, so you got a cache hit. The "Served from cache" badge in the results header shows who ran it first and when.
"Website Crawl returned 'info@example.com' as the only email" Some sites only publish a generic catch-all email. The quality tier badge tells you. Try a deeper crawl or pivot to Prospect Finder to find named contacts at the company.
"My search crashed after the credit was debited" Credits are automatically refunded when a paid search hits a fatal error before returning results. Check your transaction log for the refund line; if it didn't fire, ping support and they'll restore the credit manually.
Open Lead Finder

Tasks

The Tasks tab is your daily action queue: every manual task you've created, every active sequence step that's due, and every overdue customer check-in surface here in one list, sortable + filterable + bulk-actionable. The Dashboard's "Due Tasks" card reads from the same queue, and Play Mode consumes tasks one at a time for focused execution.

Where tasks come from

  • Manual tasks -- you create them with "+ Task" on the contact page, the Tasks tab, or from a task template.
  • Sequence tasks -- when a contact's active sequence reaches a step that needs human action (Call / Email / Manual Task), the step shows up in the queue. Completion advances the sequence.
  • Customer check-ins -- if a customer's nextCheckInDate is past, a pseudo-task surfaces in the queue. Completion writes the check-in to the contact's activity log + recomputes the next date based on cadence.

Status lifecycle

Tasks are either open or completed. Open tasks split visually into overdue (red), due today (yellow), and upcoming (neutral). Completion is irreversible from the queue -- you can delete a wrongly-completed task from the contact's history if needed, but the activity log entry stays put.

Snooze, defer, reassign, prioritize

  • Snooze -- hide the task from your queue until a specific time. Used when "I can't act on this right now, ping me later."
  • Reschedule -- change the due date itself (not just snooze). Used when the actual deadline moved.
  • Reassign -- hand the task to a teammate. Reassignment now writes an entry to the contact's Activity Log (old assignee -> new assignee + timestamp) so the audit trail isn't silent the way it used to be.
  • Priority -- Urgent / High / Normal / Low. Sort by priority to surface what matters first.

Bulk operations

Select multiple tasks with the row checkboxes; the sticky bottom bar exposes:

  • Complete -- now reports the actual completion count (e.g. "Completed 11 of 12 tasks (1 skipped -- not in your list)") instead of falsely assuming all-or-nothing
  • Reassign -- bulk hand-off to a teammate
  • Snooze -- bulk push out
  • Delete -- with confirm; permanent
If the server save fails mid-bulk, the local changes revert and the UI matches server reality. Previously a failed save left the queue in a confusing half-state that next refresh would silently "fix".

Play Mode

The Play button on the Dashboard's Due Tasks card opens Play Mode: a full-screen overlay that surfaces tasks one at a time with the action button (Call / Email / Text / Mark Complete) right there. Completing one auto-advances to the next without leaving the modal -- the elimination of context-switching is why most reps process 2-3x more tasks per hour in Play Mode than navigating normally.

Templates

Save a checklist of related tasks as a template ("Onboarding", "Post-demo follow-up") and apply it to any contact with one click. The template stamps each task with its relative offset (e.g. "Day 3", "Week 2") so the dates fill in automatically from the application date.

Filters + saved views

Filter by owner (mine / team), priority, status, source (manual / sequence / check-in), and overdue / today / upcoming buckets. Sort by due date, priority, contact name, or last activity. The active filter persists across tab switches within the session.

Activity log integration

Every task completion writes to the contact's activity log AND to the unified Activity Log (event_type task_logged). Task reassignments now also write a system_event entry so the trail of who-owned-what-when is captured.

What's deferred

A few task features have schema + server support but haven't shipped a UI yet:
  • Kanban / calendar view -- only the list view ships today. Use the Projects tab if you want a Kanban for project-scoped items.
  • Subtasks / parent_task_id -- the DB supports nesting, but the cards don't render children yet.
  • Recurring tasks -- recurrence_rule is on the schema, but the UI to set "every Monday" isn't wired into the task editor yet. Saved templates work fine as a one-time apply.
All three are on the roadmap.

Common confusion points

"My sequence task disappeared from the queue but the sequence is still active" Sequences that are paused still surface their current step as a task. If you completed it, the sequence advanced to the next step + the new step's task replaces the old one. If the new step is auto-sent (Email send-now step), no task surfaces and the queue just moves on.
"Overdue label looks wrong after I traveled to a new time zone" Overdue is computed against your browser's local time. If you crossed time zones since the task was created, the boundary may not match the original due time-of-day. Reschedule the task to fix.
Open the Tasks tab

My Work (cross-project items)

Different from the Tasks tab. Tasks surfaces engagement work — sequence steps, customer check-ins, manual outreach to-dos. My Work surfaces project items where you're the assignee, rolled up across every Job and Internal Project you're on. Two separate queues, two separate lifecycles, two separate mental modes. Both deserve daily attention; neither replaces the other.

The cross-project query

My Work pulls every row from project_items where assignee_id = me (scope = Mine, default), joined with the parent project and the column's status definition. Scope = All widens to every item in your org regardless of assignee — useful when covering for a teammate or auditing the team's backlog.

Filters + group-by

  • Filter: Open / Completed / Overdue / Due this week / Today / Snoozed / All
  • Group by: Project (default) / Status / Priority / Due date
  • Tag search: surfaces only items carrying a chosen label — useful when a tag spans multiple projects (e.g. q4-launch, permit-pending)
Date arithmetic is now local-timezone aware. The filter previously computed "today" from the server's UTC clock — at 11pm PST Thursday it silently shifted to Friday's items, and at 9am Friday Tokyo it shifted backward to Thursday's. The new code uses your browser's local date so the boundary lines up with what you typed in the date picker.

Today view

Toggle the Today view at the top of the tab to collapse the filter dropdown into one button. Today = open AND (overdue OR due today), minus actively-snoozed items. Your morning triage view — three minutes to walk it and pick what matters today.

Stats strip

The strip at the top of the tab shows:

  • Done this week (with percent change vs last week — "+ new" sentinel if last week was zero)
  • Done this month
  • Open + overdue (red when > 0)

Counts run as parallel COUNT queries against project_items, scoped to Mine or All to match the filter strip below. Honest aggregates — no per-item enumeration, no row-limit caps.

Snooze

Click the snooze icon on any item to hide it from Today / Open until a chosen time: 1 hour, 4 hours, tomorrow, 3 days, 1 week, or a custom datetime. The item keeps its original due date — snooze is purely a presentation filter that hides the row until snoozed_until passes. The Snoozed filter surfaces actively-snoozed items so you can manage them (un-snooze, edit, complete) without waiting.

Bulk actions

Tick checkboxes on multiple items to reveal the sticky bulk action bar:

  • Mark Done — looks up each item's project's Done column and moves them there (each project can have its own Done column id; one SQL UPDATE per item, all fired in parallel)
  • Snooze — same presets as single-item snooze, applied to all selected at once
  • Reassign — pick a teammate; updates assignee_id on each selected item
  • Delete — permanent, with confirm
Selected IDs that scroll off the visible set (e.g. you ticked an item, then changed filter to a view that excludes it) are dropped from the selection set automatically — the bulk bar always reflects what's actually in view + actionable.

Keyboard shortcuts

  • J / K — move highlight down / up
  • Space — toggle Done on the highlighted item
  • S — snooze the highlighted item (opens the snooze picker)

Shortcuts only fire while My Work is the active tab AND you're not typing in an input. Mirrors the Tasks tab's keyboard model.

What's deferred

My Work inherits the same scaffolded-but-not-wired features as Jobs & Projects:
  • Time tracking — when the in-item log-time UI ships, My Work cards will show estimate-vs-logged progress; today the columns exist but no UI writes them.
  • Bulk time logging — once time tracking ships, "log 1 hour to all 5 selected items" will join the bulk action bar.

Common confusion points

"I see items I'm not assigned to in My Work" Check the Scope dropdown at the top. Mine = items assigned to you (default). All = every item you can see in your org. Switch to Mine for a focused daily-driver view.
"My snoozed item is gone, not just hidden" Snooze is per-filter, not per-item. The default Open filter hides snoozed items; the Snoozed filter surfaces them. Click Snoozed in the filter dropdown to find an item you snoozed earlier.
"Today view shows items due tomorrow" This was the timezone bug fixed in the recent polish pass. If you still see future-dated items in Today, force-refresh the page so the local-date fix loads.
Open the My Work tab

Email & Inbox

The Inbox tab is your full conversation history with every contact, threaded by Gmail or Outlook depending on which provider you connect. Sends go out from your real address (not a no-reply), opens and clicks are tracked, and replies land back in the contact's activity log automatically. New messages show up in the inbox in real time — you don't have to hit refresh.

Connecting Gmail or Outlook

Go to Settings → Communication → Email Integration. Pick a provider, click Authorize, grant the permissions Google or Microsoft asks for (read + send + modify on your mailbox), and the connection is live. The CRM keeps the connection alive automatically — you don't have to reconnect every hour.

If you ever see "Real-time sync paused" in the inbox header, the live connection dropped. Reconnect from Settings → Email Integration and it relights.

Send-as aliases

Gmail and Outlook both let you send as a different verified address (e.g. sales@yourcompany.com instead of your.name@gmail.com). Configure the alias in your Google / Microsoft account first, then add it under Settings -- Email Integration -- Send-as overrides. The CRM uses the override on every outgoing send.

If the provider rejects the alias at send time ("Not a valid From: header"), the alias isn't authorized on your Google/Microsoft account. Add it via Gmail Settings -> Accounts -> "Send mail as", verify the address, then the CRM send will succeed.

Composing a message

  • Templates + variables -- pull from your saved templates; {{first_name}} / {{company}} / {{your_name}} tokens auto-substitute from the contact + the sender.
  • Attachments -- click the paperclip; provider limits apply (25 MB Gmail, 35 MB Outlook).
  • Schedule send -- pick a future time; the email queues up and sends itself at that moment, even with the CRM tab closed.
  • Signature -- toggle on/off per-send; default comes from Settings -- Account -- Email Signature. Rich-text signatures with inline images are preserved.
  • Video -- click the camera icon in the compose toolbar to record / pick from the Video Library and embed a tracked thumbnail.

Threading + conversation view

The Inbox tab defaults to Threaded view — one row per conversation, with reply counts and last-activity timestamps. Flat view shows one row per message. The CRM uses your provider's own threading, so replies from the contact's side stay attached to the right conversation even if the subject changes.

Tracking

  • Open tracking -- a tiny invisible image is embedded in every outgoing send. When the contact's mail client loads the image, the message is marked Opened in your inbox.
  • Click tracking -- links in the body are rewritten so click-throughs roll up in your reporting.
  • Replies -- inbound messages are matched to the original send automatically; matching replies are stamped on the contact's activity log without you doing anything.
Open tracking is fragile by design (corporate proxies, image blockers, prefetchers). Use it as a directional signal, not as gospel. Clicks and replies are more reliable.

Bounces + suppression

Hard bounces (5xx -- invalid mailbox) auto-add the address to your suppression list and skip on future sends; soft bounces (4xx -- temporary) accumulate over multiple sends before triggering suppression. The full bounce log lives in Settings -- Communication -- Email Integration -- Bounce + Suppression, and you can manually lift a suppression from there.

Activity log integration

Every email send writes an entry into the unified Activity Log (event_type email_sent) plus the contact's own logs array. Replies write email_replied; opens and clicks fire email_opened / email_clicked. Filter the Activity Log by event type to see just email activity for a person, a team, or the whole org.

Common confusion points

"My inbox isn't updating in real time" Check Settings -- Email Integration -- the connection panel shows the last successful watch renewal. If it's older than 48 hours, click Reconnect; the scheduler will pick it up on the next run.
"Sequences sent but I don't see them in the inbox" Sequence sends and ad-hoc compose sends both land in the inbox by default. If you filter by Direction = Inbound, they're hidden -- switch to All or Outbound to see them.
"Send-as alias rejected at send time" The provider rejected the alias as unauthorized. Go to Gmail Settings -> Accounts -> "Send mail as" (or the Outlook equivalent), verify the alias, then the CRM send succeeds.
Open the Inbox

Sequences

A sequence is an ordered list of touchpoints -- emails, calls, texts, and manual tasks -- that you assign to a contact. Each step has a relative delay ("3 days after the previous step"), automated steps fire on their own, and manual steps surface in the Tasks queue when they come due. The Sequences tab is where you build + maintain the playbooks; enrollment happens from the contact panel, the Bulk view, or automatically on lead conversion.

Step types

  • Email -- subject + body, with {{first_name}} / {{company}} / {{your_name}} token substitution. Marked Automated sends from your connected Gmail/Outlook on schedule. Left Manual surfaces as a task that pre-fills the compose modal when you click it.
  • Call -- a call task with an optional script. Opens the built-in dialer pre-dialed to the contact's number on click.
  • Text -- an SMS step. Sends from your registered 10DLC or toll-free number; same STOP-keyword + suppression enforcement applies.
  • Manual Task -- free-form description ("send LinkedIn connect request", "drop off swag bag"). Marks complete with a checkbox.
There's no standalone Video step type -- videos attach to Email steps. Click the camera icon in an Email step's editor to record or pick from the Video Library; the video embeds as a tracked thumbnail in the outgoing email and engagement (sends, plays, watch %) reports under the sequence.

Building a sequence

  1. Click + New Sequence, name it.
  2. Add steps in order. For each step: pick the type, give it a name, fill in the content, and set the delay from the previous step (days / hours / minutes -- 0 means "send immediately").
  3. Save. The editor blocks save if any step is missing a name, any Email step is missing a subject, or any delay is negative -- the validations exist because silent saves at those points used to result in failed sends with no clear error.

Variable substitution + HTML safety

Tokens in email bodies and subjects substitute the contact's data at send time: {{first_name}}, {{last_name}}, {{company}}, {{email}}, {{phone}}, {{your_name}}, {{your_company}}, plus any custom field by its key. Both {{token}} and {token} forms work; matching is case-insensitive.

For HTML email bodies, the substituted values are now HTML-escaped (e.g. a contact with the company name "Smith & Co" renders as Smith &amp; Co in the source but displays as "Smith & Co" in the inbox -- safe against <script> injection from imported data). Plain-text and SMS sends still substitute raw, since there's nothing to escape.

Enrolling a contact

  • From the contact panel -- click the Sequence dropdown, pick a sequence, optionally pick a starting step (mid-sequence enrollment uses that step's own delay as if the previous step had just completed).
  • From the Bulk Enrollment view -- filter or search for the cohort, tick the checkboxes for who to enroll, pick the sequence + starting step, click Enroll. The preview chip at the top tracks "X of Y will be enrolled" live as you check + uncheck.
  • On lead conversion -- if you've set an auto-onboarding sequence under Settings, every newly-converted lead is enrolled at Step 1 automatically. The Convert modal also offers a one-time sequence picker that overrides the default for this conversion.
Duplicate-enrollment guard. If you try to enroll a contact who's already on the same sequence with an active current step, the CRM warns you -- the per-contact picker pops a confirm; the bulk view counts how many of the selected contacts are already enrolled and confirms before proceeding; the automated post-conversion path silently skips re-enrolling them. Without the guard, the contact's prior scheduled_emails row stays pending alongside the new one, so the same email sends twice.

How automated steps fire

When a contact is enrolled at a step (Step N) where the next step (Step N+1) is an automated Email, the CRM queues the email for send at the computed time. Every minute, the queue sweeper picks up due emails one at a time, sends them through the rep's connected mailbox, and advances the sequence to the next step (writing the new current step + due date back to the contact and queuing the next automated step if there is one).

Each queued email gets claimed by exactly one sweep pass — so a temporary blip that re-triggers the sweeper inside the same minute can't end up sending the same email twice. A stuck-send recovery sweep also runs every five minutes to release any send that hung mid-flight, so a glitch can't block a sequence forever.

How manual steps fire

Manual steps (Email-without-Automated, Call, Text, Manual Task) surface as tasks in the contact owner's queue when the step's due time arrives. The owner sees them in the Tasks tab + on the Dashboard. Completing the task advances the sequence to the next step (and queues the next automated send if applicable). If you ignore a manual task past its due date, the step stays open until you act -- sequences don't auto-skip.

Removing or changing a sequence

Picking "(none)" from the sequence dropdown removes the contact from the active sequence + deletes any of their pending scheduled_emails rows so nothing sends after they're off the playbook. Switching them to a different sequence does both at once: it clears the pending rows from the old sequence, then enrolls them fresh at Step 1 of the new one.

What's deferred

A few sequence features are on the roadmap but not shipped:
  • Auto-pause on reply -- targeted for Q1 2026. Today, if a contact replies to a sequence email, the rest of the steps still send; you need to manually remove them from the sequence to stop.
  • Conditional branching -- "if opened, send A; if not, send B" style logic. The reporting infrastructure (open + click events) is in place; the editor UI for branches isn't.
  • Status/tag/form-submission triggers -- today's triggers are: manual enrollment (per-contact + bulk), lead-conversion auto-onboarding, and the Convert modal one-time picker. The marketing copy lists more triggers as on-roadmap; those land later.

Common confusion points

"I removed someone from the sequence but they still got the next email" The pending-row cleanup runs at the moment you change the sequence dropdown. If a scheduled send was already claimed by the scheduler (status flipped from pending to sending) within the second you clicked "remove", it goes out. To kill an in-flight send you'd have to revoke at the provider -- not the CRM. In practice this is a once-a-year edge case.
"Why did my contact get this email at 2am their time?" Sequence delays are computed off the enrollment moment in UTC. There's no per-contact send-window today (also on the roadmap). If business-hour-only sends matter, set the sequence's first step's delay so Step 1 lands in business hours and the cadence rolls forward from there.
"My re-enrolled contact got the same email twice" This was the failure mode the duplicate-enrollment guard above prevents. If you saw it before the guard shipped: the old scheduled_emails row didn't clear when you re-enrolled, so the scheduler sent it alongside the new one. Going forward, the guard's confirm prompt catches this before the second row is written.
Open the Sequences tab

Drip Campaigns

Drip Campaigns are email-only nurture flows aimed at a subscriber list (separate from your CRM contacts). Every list member auto-enrolls into the campaign on the next hourly scheduled run; each step's HTML body fires through the campaign owner's connected Gmail/Outlook on its day offset. Use this when you want marketing nurture to a cohort. Use Sequences instead when you want 1-to-1 multi-channel outreach to individual contacts.

Subscriber lists vs. CRM contacts

Subscribers live in a separate subscribers table — they came in via a web-form opt-in, manual addition, or CSV import. They are NOT the same rows as your CRM contacts, though the send-time personalization will look up the matching contact (by email) so tokens like {{company}} and {{title}} resolve to the same values you'd see on the contact panel. The Manage Lists button in the Drip Campaigns header is where you create + edit lists.

Tag-based audiences exist as a target option but auto-enrollment runs against list-based audiences only. If you want tag-targeted nurture, build a list-membership rule that mirrors the tag, or enroll manually from the contact panel.

Building a campaign

  1. Click + New Campaign, name it, pick the audience list.
  2. Add steps. Each step has a day offset (Day 0 = on enrollment; Day 3 = three days after enrollment), a subject, and a rich-text body.
  3. Steps must be in chronological order. Saving rejects "Step 2 at day 3" sitting after "Step 1 at day 7" — the send-time math is based on day offsets from enrollment, so out-of-order steps would deliver in the wrong sequence.
  4. Per-step Preview renders the email against a sample contact so you can verify variable substitution + the rendered body before activating.

How sending works

The drip processor runs hourly. Each pass it does two things:

  1. Auto-enrolls any new list members into every active campaign that targets their list, starting them at Step 1 with the right send time based on the step's day-offset.
  2. Sends every step that's now past due. Each enrollment is sent exactly once per tick — a temporary blip that re-runs the processor inside the same hour can't double-send.
Because the drip processor runs hourly (Sequences run every minute), a Day 0 step may not fire instantly on enrollment — it sends on the next tick. Worst case is about 59 minutes of delay. If you need instant-delivery on opt-in, use a Sequence triggered by the same form instead.

A/B variants

Per step you can add weighted variants — each variant overrides the step's subject and/or body. At send time the system picks one variant per recipient using weighted random selection, records which one you got, and rolls up the engagement metrics separately so the Variant Performance card can show "Variant A had a 24% open rate, B had 31%" without you exporting anything.

Quiet hours

Each campaign can restrict sends to a specific window — e.g. "weekdays 9–5", "every day 10am–6pm", or "overnight 10pm–6am". Off-window steps defer to the next allowed moment instead of firing at 2 AM. Configured per-campaign under the editor's Quiet Hours toggle. Times are evaluated in UTC, so set your window relative to UTC rather than your local zone.

Stop conditions

  • Stop on reply — when a recipient replies to any sent step, their enrollment auto-pauses and subsequent steps are skipped. Toggle is per-campaign, so a "welcome series" can keep going after replies while a "re-engagement" campaign stops.
  • Stop on click — same idea but fires on link clicks instead of replies. Useful for re-engagement flows where any signal of life ends the nudges.
  • Manual pause — reps can pause individual enrollments from the recipient list at any time. Paused recipients are skipped on every subsequent tick until you unpause.
  • Unsubscribe — every send includes an unsubscribe link. A click immediately removes the recipient from future sends across every campaign.

Sender identity overrides

Per campaign you can set a custom From name (e.g. "Customer Success Team") and Reply-to address (e.g. cs@yourcompany.com). Useful when the campaign creator's personal inbox shouldn't field replies. The save flow validates the reply-to email format before persisting — a typo here used to silently ship with every send and bounce every recipient reply.

Trial vs paid

Trial accounts can build drip campaigns but not send them. The processor silently skips any campaign owned by a trialing account (resolved via the org owner's subscription, not the creator's — team-member-created campaigns work fine on a paid org). When the org converts to paid, existing campaigns resume sending on the next scheduled run.

Reporting

Drip Campaign Performance lives in the Reports tab. It surfaces per-campaign sends, opens, clicks, replies, and (when variants exist) per-variant engagement breakdowns. Drip Campaigns is also a first-class source object in the Report Builder, so you can cross-cut "sends per rep × campaign status × month" or "campaigns owned by trialing accounts" without leaving the product.

What's deferred

A few drip features are on the roadmap but not shipped:
  • Conditional branching — "if step 1 opened, send A; if not, send B" style logic. The reply/click events are already tracked, but the editor UI for branches isn't built.
  • Per-subscriber timezone — quiet hours are evaluated in UTC, not the recipient's local time. A "9-5 in their timezone" feature would require capturing timezone on the subscriber row first.
  • Holiday calendar — quiet hours support weekday filtering but not a "skip federal holidays" toggle.

Common confusion points

"A subscriber I just added to the list isn't receiving anything" Auto-enrollment runs on the hourly scheduler, not on list-add. Wait up to one hour for the first scheduled run to pick them up + schedule Step 1.
"I activated the campaign but nothing sent" Check: (1) is the campaign status active (not draft / paused)? (2) is the account paid (trial accounts can build but not send)? (3) is the quiet window blocking the current hour? (4) are there subscribers on the audience list at all?
"My quiet window goes from 22:00 to 06:00 and the scheduler locked the campaign" Overnight windows that cross midnight are supported. If you're still seeing the lock, re-save the campaign so the latest validation runs.
Open the Drip Campaigns tab

Newsletters

Newsletters are one-shot email broadcasts to a chosen audience. Three send modes: Send Now (immediate), Schedule (fires at a future date/time on the hourly scheduler), or Recurring (weekly/monthly cadence, fires a child newsletter into the queue each occurrence). Distinct from Sequences (multi-channel, 1-to-1) and Drip Campaigns (multi-step, auto-enrolling). Use Newsletters for monthly updates, promo blasts, and announcements.

Audience

  • Subscriber List — pick a list managed under Marketing → Manage Lists. Lists are populated via web-form opt-ins, manual additions, or CSV import.
  • Tag-based — pick a tag (e.g. "customer" or "quarterly-vip") that exists on your CRM contacts. The system pulls every matching contact across your org and matches each to a subscriber by email. Contacts that don't have a subscriber record yet are skipped on this send and picked up next time after they're added as a subscriber.
Audience dedup. If the same subscriber appears twice in your list (or on two lists rolling up to the same audience), they only receive ONE email. Dedup happens at the audience-resolve step before rows hit the queue, so every recipient gets exactly one send no matter how many duplicate memberships exist.

Building a newsletter

  1. Click + New Newsletter. Required: an internal name, subject line, audience, and HTML body (Quill rich-text editor).
  2. Optional: a From name override (e.g. "Customer Success Team"), a Reply-To override (e.g. cs@yourcompany.com), A/B subject variants, a manual plain-text body or auto-generate from HTML.
  3. Preview renders desktop + mobile side-by-side against substituted variable values. HTML is sanitized in the preview so a teammate-authored body can't run JS in your session.
  4. Save. The save flow rejects: missing name/subject/audience/body; Schedule enabled with no time picked; Schedule enabled with a past date (the old behavior silently fell through to draft, or — on an existing scheduled row — fired immediately on the next scheduled run).

Send modes

Send Now ships the newsletter through your connected Gmail or Outlook account. The send queue drains about 360 messages per hour (30 sends every 5 minutes, with a small gap between each to stay friendly with your provider's rate-limits). A 1,000-recipient blast finishes in roughly 3 hours.

Schedule queues the newsletter for a future send time. The system picks it up at the appointed hour and starts the send queue from there — nothing for you to do once it's scheduled.

Recurring sets up a weekly or monthly cadence (pick the day + time). Each occurrence creates a fresh send tied back to the template, so reports can group "all sends from the monthly product update template" together.

A recurring newsletter is guaranteed to fire exactly once per occurrence — even if the behind-the-scenes timer ticks twice in the same minute (which can happen under heavy load), the second tick sees the work already claimed and bows out instead of double-sending.

A/B subject variants

Add multiple subject lines with weights. As each recipient sends, the system picks one variant using weighted random selection and records which one they got. Open/click rates roll up per variant in reports so you can spot "Variant B had 31% open vs A's 24%" without exporting anything. Body variants aren't supported in newsletters — that's a Drip Campaigns feature.

Stop-on-reply

Opt in per newsletter. When any recipient replies to a sent newsletter, their remaining queued sends are paused automatically. Useful for a flash-sale blast where replies are the goal — once someone asks about the sale, you don't want their follow-ups in the queue going out the same hour.

Templates

Save any newsletter as a reusable template via the Templates panel. The next time you click + New Newsletter from Template, you start with the template's subject, body, sender identity, and audience pre-filled. Useful for the "monthly product update" cadence where the structure is consistent but the content changes.

Trial vs paid

Trial accounts can build newsletters but not send them. Same paid-only gate Drip Campaigns use — the scheduler silently skips trial-owned newsletters. When the org converts to paid, scheduled/recurring newsletters resume on the next scheduled run. The send-marketing-email endpoint also rejects newsletter sourceTypes for trialing accounts as a second line of defense.

Personalization

Same {{firstName}} / {{lastName}} / {{company}} / {{title}} / {{city}} / {{state}} tokens as drips. Resolves against the subscriber row + any matching CRM contact (by email). Unknown tokens resolve to empty string — don't leave the literal {{token}} in the email.

Reporting

Newsletter Performance lives in the Reports tab — sends, opens, clicks, replies, unsubscribes per newsletter, plus per-variant breakdowns when variants exist. Newsletters is also a first-class source object in the Report Builder, so you can cross-cut "sends per newsletter × month" or "newsletter-driven opens per subscriber list" without leaving the product.

What's deferred

A few newsletter features are on the roadmap but not shipped:
  • A/B body variants — only subject variants today. Body variants would be a useful extension for testing different copy / layouts.
  • Quiet hours per newsletter — drips have a per-campaign quiet-hours window; newsletters fire whenever scheduled. A "don't send Sunday 2am" toggle is on the list.
  • Per-recipient send-time optimization — no "smart timing" yet. Sends fire when the queue drains, not when each recipient is most likely to open.
  • Auto-task on click — clicks log to the recipient's timeline but don't auto-create a follow-up task for the contact owner. Use a drip with stop-on-click to capture the engagement signal in the meantime.

Common confusion points

"I clicked Send Now and only a few have received it after an hour" That's the batched queue working as intended. 30 sends per 5-minute batch = about 360 per hour. A 2,000-recipient blast finishes in about 6 hours. If you need to send faster, contact support — the conservative batch size keeps Gmail / Outlook happy with your sending volume.
"My recurring newsletter fired twice this week" If you change the day-of-week or time on a recurring template mid-week, the next-send math may compute a date that still falls in the same week as your last send — producing two sends. Each individual scheduled run is guaranteed not to double-fire, but a change to the recurrence pattern can shift the next occurrence forward.
"I scheduled a newsletter for next month and it sent today" This was the failure mode the past-date save guard fixed. If you saw it before the patch shipped: you most likely typed a past date by accident (the old code silently fell through to draft on new rows but fired immediately on edited scheduled rows). Going forward, saving a past-date schedule is rejected at save time with a clear error.
Open the Newsletters tab

Scheduled Posts

Scheduled Posts is a reminder-based content library for LinkedIn and Facebook posts. Compose a post in the CRM, set a scheduled time, and when that time arrives the CRM fires an in-app notification ("Time to post on LinkedIn") so you publish manually to your social pages. There's no auto-publish today — the in-app "Library mode" banner at the top of the tab makes this explicit. The OAuth plumbing for direct publishing to LinkedIn Pages and Facebook Pages is on the roadmap.

Composer

Click + New Post to open the composer. The first decision is platform — LinkedIn (3,000-character limit) or Facebook (63,206). The composer enforces the platform's character cap with a live counter that turns yellow at 80% and red over the limit. Toggling platform mid-compose updates the placeholder copy + the audience label + the primary button to match.

  • Content — the body that will go on the platform.
  • Hashtags — legacy field; hashtags now live inline in the body. Anything you save here renders as a separate row beneath the post body for backward compatibility.
  • Link URL — renders as a link preview (LinkedIn / Facebook auto-fetch the OG card when you actually paste it; the preview here is approximate).
  • Media — comma-separated image URLs OR uploaded via the upload helper (10 MB cap per file, JPEG/PNG/GIF/WEBP only — SVG is rejected because of the inline-script XSS risk on the public bucket URL).
  • Scheduled for — date/time picker. Setting this moves the post into Scheduled status; leaving it empty saves as Draft.
  • Notes — private notes visible only to you, never part of the published post. Use for "ask Sarah for approval before posting" or revision history.
  • Cross-post toggle (when first creating a post) — when enabled, saves the post as two separate posts (one LinkedIn, one Facebook) linked together so reports can group them. Edits to either side are independent after the cross-post.

Statuses

  • Draft — saved but no schedule set.
  • Scheduled — has a future scheduled time. Notification fires when the time arrives.
  • Posted — you clicked the green check on the card after publishing manually to the platform. Timestamp recorded.
  • Archived — hidden from the active grid but kept for history. Use the Archived filter to find them; click the unarchive icon to restore.

How the reminder fires

While you have a CrawlSpace tab open, the CRM checks every 60 seconds for any of your scheduled posts whose time has arrived. When it finds one, it claims the reminder slot (so two browser tabs can't both fire the same alert) and posts a "Time to post on LinkedIn / Facebook" notification with the post preview and a click-through to the Scheduled Posts tab. The card flips to a pulsing "Due now" pill until you mark it posted.

The 60s check only runs while the CRM is open in your browser. If you close all tabs, no notification fires until you reopen. To survive this you can: (a) keep a CrawlSpace tab open in your morning browser window, (b) use the sidebar's "due today" badge as a glance-check when you do open the app, or (c) wait for the OAuth direct-publish rollout which moves the timing trigger server-side.

Sidebar badge

The Scheduled Posts nav item shows a red badge with the count of posts that are past their scheduled time but not yet marked as posted. Glance at the sidebar to spot overdue posts without opening the tab.

Mark Posted / Archive / Delete

After publishing the post manually to LinkedIn or Facebook, click the green ✓ on the card. The card flips to Posted and stops triggering reminders. The Posted status unlocks an "add performance metrics" action so you can paste in the actual likes/comments/shares from the platform — useful when reports group sends by author or month.

Archive hides the post from the active grid without deleting it. Delete is permanent and owner-only — teammates without ownership can archive but not delete.

Recurring posts

A scheduled post can be marked recurring with a weekly (day-of-week) or monthly (day-of-month) cadence + a fire time. The recurring the scheduler materializes a child post for each occurrence (banner label "from recurring") so you can edit individual occurrences without breaking the template. Useful for "every Monday morning at 8am" sales-team posts.

Templates

Save any composed post as a template (Save as Template button in the composer). Future composes can start from a template via the Templates button — pre-fills body / hashtags / link / media. Useful for the "Friday wrap-up" or "new-customer welcome" cadence where the structure is consistent and only the content changes.

Org-shared library

Posts are scoped to your org (or to your user if you have no org). Team members can see and edit any post in the org library. The owner gate is per record — only the original author (or an org owner / admin) can permanently delete; teammates can archive instead. Notification reminders only fire to the owner so two reps don't both get pinged for the same post.

Filtering, search, calendar view

The toolbar has search (matches body, hashtags, notes), platform filter (All / LinkedIn / Facebook), status filter (Drafts & Scheduled / Draft only / Scheduled only / Posted / Archived / All), owner filter (Mine / Team), and sort (Soonest first / Newest created / etc). The calendar view shows scheduled posts on a month grid for visual cadence sanity-checking.

What's deferred

A few scheduled-posts features are on the roadmap but not shipped:
  • Direct OAuth publishing — V2 will add a "Publish Now" button next to "Mark as Posted" once you OAuth into LinkedIn Pages or Facebook Pages. The reminder model carries over for accounts that aren't connected.
  • Server-side reminder firing — today the 60-second notification check runs in your browser, so closing the tab pauses reminders. A future server-side reminder system will fire reminders as email or in-app notifications regardless of whether you have the CRM open.
  • Multi-image carousels — the composer allows multiple image URLs but the preview renders the first four as a grid. True carousel support comes with the OAuth publish flow.
  • Post-engagement analytics — V2 will fetch engagement metrics back from LinkedIn/Facebook into the contact's engagement score. Today you manually enter likes/comments/shares on the Posted card.

Common confusion points

"I scheduled a post for Tuesday morning and no notification fired" Was your browser open with a CrawlSpace tab at the scheduled time? The 60s check runs client-side only. The sidebar badge will still show the overdue count the next time you open the app, and the "Due now" pill on the card persists until you mark it posted — so you'll catch it on your next session even if the instant notification missed.
"My SVG logo upload was rejected" Intentional. SVGs can carry inline <script> blocks that execute when the file is fetched directly from the public bucket URL (e.g. when a teammate shares the URL as "check out this image"). Use PNG or JPEG instead — LinkedIn and Facebook also don't accept SVG post media.
"Two browser tabs both fired the reminder" This was the failure mode the double-fire guard fixed. Going forward, the first tab to claim the notification slot wins; the second tab sees zero rows back from its update and bows out, mirroring the stamp in its local cache. If you still see duplicates, refresh both tabs so they pick up the latest code.
Open the Scheduled Posts tab

Marketing & Forms

The Marketing tab is your lead-capture hub. It bundles four related surfaces that all feed inbound leads into the same CRM contacts table: the Web Form Builder (drag-and-drop forms embedded on your site), QR Forms (the same forms shared via a scannable QR code), Facebook Lead Ads auto-import, and LinkedIn Lead Gen Forms auto-import. Each source independently checks the incoming email against your existing CRM contacts before creating a row, so a lead who fills out a Web Form, then a Facebook Lead Ad, then a QR Form ends up as one contact with three lead-source labels appended — not three separate rows.

Web Form Builder

Click + Create Form on the Marketing tab to open the builder. Split-view editor (left = field list + settings, right = live preview). Drag / add fields, set per-field labels + types + CRM mappings, style the submit button + success message, and save. The form gets its own UUID-keyed URL the moment you save.

Save guard. Saving rejects forms with no name, zero fields, or any field missing a label — each of those used to ship a broken embed that quietly did nothing when submitted. If you see "Add at least one field before saving," you've hit the new guard.

Embed

From the form list, click Embed. A modal shows you a self-contained HTML+JS snippet (no external CSS or JS dependencies) — paste it into any page on any platform (WordPress, Squarespace, Wix, raw HTML, etc.). The snippet creates the form, handles client-side validation, POSTs to a public /.netlify/functions/form-submit endpoint using only the form's UUID key, and shows the success message inline (or redirects to your configured thank-you URL).

QR Forms

From the form list, click the purple QR Code button. The modal generates a 400x400 PNG via qrserver.com, with copy-link, download, and print actions. Scanning the QR opens a mobile-friendly hosted version of the same form. QR submissions are tagged QR Form: [name] instead of Web Form: [name] so reports can split event/trade-show leads from website leads.

Form submission pipeline

  1. Honeypot check — every form embeds a hidden _cs_hp field. Bots fill it; humans don't see it. If filled, the submission silently 200s (bot thinks it worked) and no lead is created.
  2. Rate limit — max 15 submissions per IP per form per hour (returns 429 over the cap).
  3. reCAPTCHA v3 — optional per-form layer. Enable on the form + paste your Google reCAPTCHA secret key in Settings → Marketing. Scores under 0.5 silently reject.
  4. Required field validation — server re-validates required fields (the client validation is just a UX hint).
  5. Dedup by email — if the submitter's email already exists in your CRM (org-scoped if you're a team, user-scoped otherwise), the existing contact is UPDATED instead of duplicated. Empty fields fill, lead-source trail appends, notes append with a timestamp. Status / lead owner / active sequences are preserved — they belong to whoever's been working that contact.
  6. Write to whichever storage mode the form owner has selected (CrawlSpace native or your connected Google Sheet).
  7. Post-submission hooks (fire-and-forget so a slow side effect doesn't block the success response): auto-assign owner (round-robin or fixed user), auto-enroll into a Sequence and/or Drip Campaign, send the submitter a receipt email, send the owner a notification email, fire Facebook Pixel / Google Tag / LinkedIn Insight Tag events on the form-page side.

Facebook Lead Ads

Connect under Settings → Integrations → Facebook. You sign in to Facebook, grant CrawlSpace permission to read your Page leads, then pick which Pages to subscribe. For each subscribed Page you set a default Tag and a default Sequence — every inbound lead from that Page lands with the tag attached and the sequence enrolling them at Step 1.

Forged-lead protection. Every inbound Facebook lead arrives with a cryptographic signature from Facebook. The CRM verifies it before creating the contact, so a forged request from anyone else gets rejected automatically — nothing for you to configure.

The same email-dedup behavior runs for Facebook leads — if a lead's email is already in your CRM, the existing contact gets updated (with "Facebook Lead Ad" appended to the lead-source trail) instead of creating a duplicate. Every Facebook webhook hit is logged inside the CRM so you can troubleshoot misses without digging through server logs.

LinkedIn Lead Gen Forms

Status: ready on our side, waiting on LinkedIn approval. The full LinkedIn integration is built — signed webhook verification, per-account routing, email dedup, sequence enrollment, everything. But LinkedIn Marketing Developer Platform approval is still in review. The Connect LinkedIn button activates once LinkedIn approves; until then, "Coming Soon" stays on the marketing page. Nothing for you to do — the integration just starts working once approval lands.

When live, the flow mirrors Facebook: connect, select your ad accounts, set per-account default Tag + Sequence, leads arrive automatically, same dedup + routing as every other source.

Lead routing across all sources

All four sources land in your CRM through the same dedup logic, so cross-channel duplicates resolve to one contact. The lead-source field accumulates a pipe-separated trail (e.g. "Web Form: Contact Us | Facebook Lead Ad | QR Form: Trade Show 2026") so the Source Performance report can attribute touches without losing earlier ones.

Marketing tab dashboard

The top of the Marketing tab shows four stat cards — one per source — with totals + this-month + this-week counts. Each card links into the underlying lead list pre-filtered to that source. The "leads over time" sparkline gives you a quick sense of which channel is accelerating without opening Reports.

What's deferred

  • LinkedIn Lead Gen — Connect button — gated on LinkedIn Marketing Developer Platform app approval. Code is live; UI activates on approval.
  • Facebook / LinkedIn lead → round-robin assignment — web forms support it; social webhooks fall back to per-page/per-account default routing. A future pass will share the post-submission hook between all three handlers.
  • Facebook / LinkedIn lead → submitter receipt + owner notification email — same gap as round-robin. Web-form-only today.
  • Form analytics by field — the per-form submission counter is in place, but "which question made people abandon" requires per-field abandonment logging we haven't built.

Common confusion points

"My Facebook lead didn't appear" Open Settings → Integrations → Facebook Lead Ads → Recent webhook activity. If there's no entry for that lead, Facebook never sent it — re-subscribe the Page in Facebook's Webhook Subscription panel. If there IS an entry but it shows an error, the error tells you what to fix (most commonly: the Page access token expired and needs reconnecting).
"Same person submitted the form twice and I only see one contact" That's the dedup behavior working as intended. The second submission filled in any empty fields on the existing contact and appended a timestamped note. Open the contact's Notes field for the resubmission timestamp; the lead-source field shows the full trail.
"My form embed shows but doesn't submit" Usually one of three things: the form was paused or the form owner's subscription lapsed, a required field is missing on the submission, or you've hit the rate limit (too many submissions from one visitor in a short window). Re-publish the form, double-check required fields, and try again.
Open the Marketing tab

SMS & Texting

SMS is a regulated channel in the US -- carriers won't deliver your messages until your business + your message content are registered (10DLC for local numbers, separate verification for toll-free). The CRM walks you through registration in-app, then routes every send + reply through your registered number with full STOP-keyword + suppression enforcement.

The painful part first: 10DLC registration

If you're using a 10-digit local number (your area code, not 800/833/844/etc), US carriers require 10DLC registration -- two steps:

  1. Brand registration -- your business name, EIN, address, use case. ~1-3 days to approve. The brand identifies who is sending.
  2. Campaign registration -- describes your messaging program: opt-in language, sample messages, expected volume. ~1-4 days to approve after brand approves. The campaign identifies what you're sending.

Both happen in Settings -- Communication -- Phone System -- SMS Registration. Total cost: $10 ($4 brand + $10 campaign, one-time, via Square). Once approved, the CRM stores the Twilio Messaging Service SID and routes every outbound send through it.

Sending before approval will fail. Carriers block sends from unregistered long-codes (Twilio errors 30007 / 30032). The CRM now pre-checks your registration state and returns a clear "your campaign is still pending approval" error instead of letting the Twilio failure surface as a cryptic "delivery failed".

Toll-free numbers (800 / 833 / 844 / etc)

Toll-free numbers have their own verification flow (separate from 10DLC). Submit your verification form in Settings -- Phone System -- Toll-Free Verification: business name, address, opt-in screenshots, sample messages. Approval takes 2-5 business days. Until then, the CRM blocks toll-free sends with a clear status message.

Sending an SMS

  • Compose -- click any contact's phone number, or the SMS icon in the dialer / sequence editor. Type your message; the segment counter shows below the textarea (160 GSM chars or 70 Unicode chars per segment).
  • Templates + variables -- pull from your saved templates; tokens like {{first_name}}, {{company}}, {{your_name}} substitute from the contact + sender. Missing fields render as empty strings.
  • MMS -- attach up to 10 images per message. MMS counts as 1 segment but charges 4 cents (vs 1 cent for SMS) since Twilio's per-MMS cost is higher.
  • Schedule send -- pick a future date/time; the message queues up and sends at the right minute, even with the CRM tab closed. Each scheduled send is guaranteed to fire exactly once — a hiccup that retries the scheduler can't double-send.
  • Broadcasts -- send one message to many contacts. The CRM staggers the queue at 5-second intervals to stay within Twilio's per-second long-code throughput.

STOP / START / HELP compliance

US carriers require honoring opt-out keywords. The CRM handles this automatically:

  • Inbound "STOP" / "STOPALL" / "UNSUBSCRIBE" / "CANCEL" / "END" / "QUIT" -- auto-inserts the sender into your suppression list. Future sends to that number are blocked at both live-send time AND scheduled-send time (the scheduler re-checks at fire time so a queued broadcast can't blow past an opt-out that arrived after scheduling).
  • Inbound "START" / "SUBSCRIBE" / "UNSTOP" -- removes the suppression. The contact can receive messages again.
  • Inbound "HELP" / "INFO" -- the CRM doesn't auto-respond; you can reply manually with your contact info.

Suppressions are per-user OR org-wide. Manual lift available from Settings -- Phone System -- Suppressions, with the original inbound message body attached for the audit trail.

Inbound conversation threading

Incoming SMS routes to the matching contact's thread via E.164 phone normalization. If the sender isn't a contact yet, the message appears in the "Unknown number" filter in the SMS tab so you can create a contact from it. MMS attachments are re-hosted into your CRM on receipt (the original media links from the carrier expire after 24 hours; the CRM keeps your copies indefinitely).

Sequences integration

SMS steps inside a sequence respect the same suppression + registration checks as direct sends. If the contact opted out, the SMS step is skipped (not failed) and the sequence advances to the next step.

Contacts also have a Texting Available field (Yes / No / Not Set). When set to No, the SMS button is disabled and sequence SMS steps skip that contact -- useful for explicit opt-in tracking on contacts you haven't gotten STOP from but never got positive opt-in from either.

Costs

  • Outbound SMS: 1 cent per segment (passthrough Twilio rates)
  • Inbound SMS: 1 cent per segment
  • MMS: 4 cents per message
  • 10DLC registration: $4 (brand) + $10 (campaign), one-time, paid via Square
  • Vetted brand (extended verification, faster throughput): +$45 one-time
  • Toll-free verification: free

SMS pulls from the same credit pool as voice calls (Settings -- Billing).

Common confusion points

"My first message got delivered, then nothing" Carriers ramp throughput slowly on a new registered campaign. Day 1 you can send a handful; by day 7-14 you'll be at your full daily quota. Bulk sends in the first week may queue and deliver gradually rather than instantly.
"Scheduled SMS sent twice" Each scheduled send is guaranteed to fire exactly once now. If you still see a double, it's most likely two separate manual schedules of the same message — check the queue view to confirm.
"Quiet hours not enforced" The CRM doesn't currently enforce TCPA quiet hours (typically 9pm-8am local to the recipient). High-volume senders should schedule manually with the recipient's timezone in mind. Built-in quiet-hour gating is on the roadmap.
Open the SMS tab

Calls & Dialer

The CRM ships with a browser-based phone (powered by Twilio) plus a Power Dialer for working through a list of contacts back-to-back. Calls auto-log, dispositions are configurable, recordings + transcripts attach to the contact, and DNC suppression is enforced at the moment of dial. No second softphone tab, no copy-paste numbers.

Setting up Twilio

Settings -- Communication -- Phone System walks you through creating a Twilio subaccount (we create it under our parent account so you don't need a separate Twilio bill), buying a local or toll-free number, and -- for toll-free numbers -- submitting the carrier verification form. Toll-free calling is gated by carrier verification; the CRM watches your verification state and notifies you the moment it approves.

Click-to-call

  • Anywhere a phone number appears -- contact card, lead row, search results, deal modal -- clicking it opens the dialer pre-loaded with the contact context.
  • Power Dialer queue -- queues a whole list. Auto-advances on disposition save so the rep doesn't have to click between calls.
  • Manual -- type any number into the dial pad and hit call.

Recording rules

Call recording is feature-gated at the org level (Settings -- Phone System -- Recording). When it's on, every outbound and inbound call records by default. You can customize:

  • Per-call disable -- toggle the mic icon in the dialer to record-this-call or not-this-call before placing.
  • Inbound announcement -- toggle the "This call may be recorded..." pre-roll for inbound calls (required in two-party-consent states).
  • Outbound announcement -- same toggle for outbound calls.
  • Announcement text -- customize the script Twilio reads.
Two-party consent states (California, Florida, Illinois, Massachusetts, Maryland, Montana, Nevada, New Hampshire, Pennsylvania, Washington) require notice before recording. Keep the announcement on for those calls. The toggle is your responsibility -- the CRM doesn't auto-detect the recipient's state.

Call dispositions

Every call ends with a disposition picker: Connected, Left Voicemail, No Answer, Busy, Not Interested, Callback, Appointment Set, Wrong Number, Do Not Call, or your own custom labels. Each disposition has:

  • Slug -- immutable analytics key (so renaming the label doesn't break reports).
  • Label -- what the rep sees; rename freely.
  • Category -- drives downstream behavior. dnc auto-adds to the suppression list. callback schedules a re-dial in the Power Dialer queue.

Admins manage the dispositions list in Settings; reps just pick from it. The defaults ship with every org via seed_default_call_dispositions().

DNC (Do Not Call) suppression

The dialer checks the suppression list before placing every call. If the destination number is flagged (the contact requested DNC, was a wrong number, etc.) you get a confirm dialog with the reason -- proceed only if the contact has since explicitly asked you to call back. Suppressions are:

  • Per-user -- "this contact asked ME not to call again"
  • Org-wide -- "no one at the company should call this number" (set by admins)

Disposition = Do Not Call auto-inserts a per-user suppression with a back-pointer to the call_record so you have an audit trail. Manual lift from Settings -- Phone System -- Suppressions.

Voicemail greetings (inbound)

Record one custom greeting per user. Inbound calls that go to voicemail play your greeting, record up to 2 minutes, drop the recording into the voicemail inbox, and trigger Twilio's speech-to-text. The transcript attaches to the voicemail row within 30-90 seconds of the recording finishing.

Voicemail drops (outbound)

Pre-record up to 5 voicemail templates ("Hi, this is Alex from Acme; give me a call back at..."). When you reach voicemail on an outbound call, click Drop in the dialer + pick a template; Twilio plays your pre-recorded message and ends the call. Cuts the per-call overhead from ~30 seconds of talking to ~5 seconds of clicking.

Voicemail drops are feature-gated at the org level (Settings -- Phone System -- Features). Owner/admin toggles it on; the dialer surfaces the Drop button only when it's enabled.

Power Dialer

  • Lists -- create a list, add members (manual, from a filter, from a saved search). Optional daily quota with a confirm-to-override modal when you hit the limit.
  • Dial mode -- queues the list, auto-advances on disposition save. Skip pushes the current member to the back of the queue.
  • Callbacks -- disposition = Callback schedules the contact for re-dial in N days. The Callbacks view shows everything due today.
  • Dispositions view -- per-list rollup of how the queue went (connected count, voicemail count, callback count, DNC count).

Business hours + after-hours forwarding

Settings -- Phone System -- Business Hours lets you configure your org's weekly schedule + holidays (IANA timezone aware). Inbound calls outside hours can:

  • Go to voicemail -- default; uses your standard greeting
  • Forward to another number -- your cell, an answering service, anyone
  • Play a custom message -- "We're closed; please email..."

Call costs

Costs pass through Twilio at-cost (no markup):

  • Outbound + inbound calling: ~$0.013-$0.022/min depending on destination
  • Local phone number rental: ~$1.15/month per number
  • Toll-free number rental: ~$2/month per number
  • Recording: ~$0.0025/min
  • Transcription: ~$0.05/min (opt-in)

Pre-flight balance check requires at least $0.10 to place a call. Top up from Settings -- Billing.

Common confusion points

"Recording URL says expired" Twilio recording URLs are short-lived. The CRM proxies playback through /.netlify/functions/twilio-recording using the recording SID (not the URL), so playback always works as long as Twilio still has the file (retention is set in your subaccount settings).
"Transcript missing on a recording I just made" Transcription runs after Twilio finalizes the recording and can lag the recording by 30-90 seconds. Refresh the call record after a minute; if it's still missing, transcription may be disabled on your org (Settings -- Phone System -- Features).
"Dial button stuck disabled" The dial button disables for the ~300ms of the pre-call DNC suppression check to block rapid-click bypass. If it stays disabled after a call ends, hit the hangup button once -- the call-end handler re-enables it.

Hold, voicemail drop, and disposition safety

Hold is a real hold (not just muting your mic) Pressing Hold puts the customer on actual hold with hold music playing on their end — not a local mute. Resume picks the call back up. If you ever see the button snap back to its old state with a "Hold failed" message, the call leg ended before the toggle could land — the call has already ended on the other side.
Voicemail drop is a manual trigger The CRM does not auto-detect answering machines. When you hear the beep, click Drop VM and your pre-recorded greeting plays into their voicemail. This is intentional — auto-detect would add 2–5 seconds of dead air on every call a real person answers, which kills connect rates.
Disposition has a close-tab safety net When a call ends the disposition modal opens so you can tag the outcome. If you close the browser tab before picking one, the call still gets saved to history with an empty disposition — you can edit it later from the Calls list. Pick a disposition normally and no duplicate is written.
Long calls keep working automatically Your calling connection refreshes itself behind the scenes about every 45 minutes so your audio session doesn't expire mid-call. If your network drops mid-refresh, the recovery handler reconnects within about 30 seconds. You shouldn't see any of this unless something fails.
Open the Calls tab

Calendar & Booking

The Calendar tab is your two-way sync with Google Calendar or Outlook Calendar -- events you create in the CRM push out to your provider, events you create in your provider show up in the CRM. On top of sync, you can publish a public booking page (think Calendly) at /book/<your-slug> so prospects pick a slot from your live free-busy without the back-and-forth.

Connecting Google Calendar or Outlook

Calendar connection rides on your email integration -- when you connect Gmail or Outlook in Settings -- Communication -- Email Integration, the calendar OAuth scope comes along. No separate "connect calendar" button. The Calendar tab uses the same access token to read + write events on your primary calendar.

Today the CRM only syncs your primary calendar. If you keep work + personal calendars separate in Google or Outlook, only the primary one's events appear in the CRM. Multiple-calendar support is on the roadmap.

Creating events

  • From the Calendar tab -- click a date / time slot to open the new-event modal. Title, attendees (auto-completes from contacts), description, location, optional video call link (Meet for Gmail, Teams for Outlook). Save pushes to the provider, which echoes back into the CRM grid.
  • From a contact -- the meeting button on the contact card opens the same modal pre-filled with the contact as the attendee.
  • From a booking page -- prospect picks a slot; the CRM creates the event on the assigned rep's calendar + creates a CRM contact if one doesn't exist + schedules reminders.

Booking pages

Settings -- Communication -- Calendar & Booking -- New Page. Configure:

  • Slug -- the public URL is /book/<slug>. Globally unique.
  • Title + description -- shown on the public page header.
  • Meeting types -- which durations the prospect can pick from (15 / 30 / 45 / 60 min, or your own custom types).
  • Weekly hours -- which hours each day are bookable. Cross-checked against your live calendar free-busy in real time so a conflicting meeting blocks the slot.
  • Buffer -- default 15 minutes before + after each booking. Prevents back-to-back meetings with no breathing room.
  • Minimum notice -- the prospect can't book a slot less than N hours out (default 2 hours).
  • Maximum advance -- the prospect can't book a slot more than N days out (default 30 days).
  • Intake questions -- short answer fields the prospect fills out at booking. Answers are stored on the booking row and shown in the calendar event description.
  • Assignment strategy -- for shared team pages: single owner, round-robin (cycles through assigned reps), or first-available.
Publish vs draft: a page in draft state isn't reachable at the public URL. Only published pages accept bookings.

Reminders

Every booking schedules two CRM-sent email reminders out of the box: 24 hours before and 1 hour before. These come from your real mailbox (Gmail or Outlook), on top of whatever popup notifications your provider fires. Each reminder is guaranteed to fire exactly once even under retry or clock skew.

Cancel + reschedule

  • Cancel from the CRM event modal -- removes the event from your provider calendar.
  • Reschedule via the booking page reschedule link -- the prospect picks a new slot; the CRM patches the existing event.
  • Provider-side delete (you delete the event in Google Calendar directly) -- next sync removes it from the CRM grid.

Spam protection

The public booking endpoint has a hidden honeypot field that catches bots. Real humans don't fill it; bots do. Triggered submissions get logged as status='spam' in the bookings table for audit, and the bot is silently returned a success response so it doesn't retry.

Slot race protection

Two prospects clicking Confirm on the same slot at the same time used to both succeed (a fairly embarrassing bug). The submit handler now pre-checks the bookings table for any existing status='created' row at the same time + same rep, and returns a 409 "that slot was just taken" if found. The window is narrowed from multi-second (the calendar API roundtrip) to milliseconds.

Costs

Calendar sync and booking pages are free with your CRM subscription -- no per-booking or per-page fees. Reminder emails go out via your connected mailbox (Gmail / Outlook) and count against your provider's daily send quota, not a CRM credit pool.

Common confusion points

"My calendar stopped updating after a few days" Most likely your provider OAuth token expired and the auto-refresh didn't run. Reconnect from Settings -- Email Integration; calendar reads use the same token.
"A prospect booked a time that was already busy" Two scenarios: (1) you added the conflicting event AFTER the prospect loaded the booking page but BEFORE they submitted -- the new race-check catches most of these; (2) the event is on a secondary calendar the CRM doesn't read. Move events to your primary calendar, or keep booking-eligible hours narrow enough to avoid the secondary-calendar conflict zone.
"Reminder email never arrived" Check Settings -- Email Integration -- Mailbox Status. Reminders send through your real mailbox; if the connection is broken, the failure shows up in the activity log so you can see exactly when and why it stopped.
Open the Calendar tab

Video Messaging

Record a personalized video right in the browser (or upload one), embed it into an email or SMS, and the recipient opens a branded landing page at /v/<token> with play tracking + a Reply CTA. Two tabs surface the feature: the Video Library (your reusable recordings) and Video Activity (every send + how it was received).

Recording vs uploading

  • Record -- two modes: Camera Only (talking head) or Screen + Camera (share a window/tab with your camera as a small bubble overlaid on the screen). For Screen + Camera you also pick the bubble shape (circle / rounded / square) and which corner it lives in.
  • Upload -- drag-drop or pick a file. Accepted formats: MP4, WebM, QuickTime / MOV. Max 3 minutes, 2 GB. The browser extracts a thumbnail at the 1-second mark; if extraction stalls (rare on huge files), the upload still completes with a neutral placeholder you can replace via Customize.

Your library

Up to 25 videos per user. The Library shows a card per video with title, duration, file size, engagement stats (sends / plays / play rate / avg watched), and chips for any customizations (chapters, CTA, custom thumbnail, team-shared). Rename inline. Delete with the trash icon -- that removes the file from storage AND any future sends point at a "video no longer available" page (existing sends keep their tracking history).

The 25-video cap is enforced on every record/upload. If you hit the cap, delete a stale recording first. Older sends of deleted videos still surface in Activity, just without the playable file.

Sharing one-to-one

  • Copy Link -- mints a fresh tracking token and copies the URL to your clipboard. Paste into any chat, email, SMS, or doc.
  • SMS -- pick a contact and send the tracked link as a text directly from the library card.
  • Email composer -- the camera icon in the compose toolbar inserts a video thumbnail + tracked link inline. The recipient sees a clickable poster.

Broadcasting to many

The Broadcast button on each library card opens a recipient picker. Two ways to add recipients:

  • Search + check from your contacts (live filter on name / email / company)
  • Paste a comma / newline / semicolon-separated list of email addresses (validated + deduplicated against your selection)

Each broadcast creates one parent send record plus one per-recipient send with its own unique landing URL, so per-recipient analytics still work. Capped at 500 recipients per broadcast; if your paste exceeds that, the modal asks whether to send the first 500 or cancel and split.

Entity linking

Each send can be tied to a specific contact, deal, or quote so the Activity Log can drill back from the send into the related CRM record. The send composer captures the contact automatically; manual linkage from the broadcast modal lets you tie sends to a specific deal or quote if relevant.

Activity tracking

  • Engagement stats -- per-send: opened, played, max watch %, watch progress bar, drop-off histogram (10% buckets).
  • Reactions + comments -- recipients can react with an emoji or leave a short comment from the landing page. Rate-limited per IP (30 reqs / 10s) to slow accidental click loops.
  • First-play notification -- the sender gets an in-app notification the first time a recipient plays the video. Subsequent plays don't re-notify (avoids spam if the recipient re-watches). The first-play event is guaranteed to fire exactly once even if the recipient hits play twice in quick succession.
  • Activity Log entry -- every send writes a "Video sent" event to the unified Activity Log; first play writes a "Video played" event (direction = inbound).

Video Activity tab specifics

The Video Activity tab in the Insights sidebar group is the unified send-by-send timeline (vs the Video Library which is the per-content view, and contact detail which is the per-recipient view).

  • 500-send cap. The tab loads the 500 most-recent sends so the page stays snappy. For high-volume reps with more than 500 sends, the KPI tiles surface "Sends shown: 500 of X" plus a yellow caveat banner. Pre-fix, the tab silently treated 500 AS the total and reported wrong play-rate / completion percentages for big senders; the true total now comes from a separate head-only COUNT query.
  • Owner vs member scope. Org owners and admins see every send across the org. Regular members see only their own sends — teammates can't see each other's individual sends unless they're owner / admin.
  • Filter dropdown -- All sends / Played only / Not yet played / Watched fully. "Watched fully" means the recipient reached 100% of the video; "Played" means any play started.
  • 30-second cache. The tab caches engagement data for 30 seconds so navigating between Library / Activity / contact panels is fast. Hit Refresh after you know someone just watched to bypass the cache.
  • "Open ↗" link. Opens the recipient's view of the video in a new tab using the same tracking token they received — useful to check what they actually saw. Caveat: clicking your own Open ↗ does generate a view-tracking row on your own send (not a sender-mode preview yet — that's roadmap).

Retention + auto-cleanup

A daily cleanup sweep removes recording files older than 30 days unless they were viewed in the last 14 days OR watched deeply (50%+) at any point. The check looks at the most-recent play on each send, so a recipient who watches at day 25 keeps the file alive until day 39. Reactions + comments on expired sends are cleaned up too, so nothing dangles.

Library videos (not the one-shot temp files) are never auto-deleted. Only temp recordings tied to specific sends are subject to the 30-day retention window.

Costs

Recording, storage, and bandwidth are included in your CRM subscription — no per-video or per-play fees. Most teams stay well within the included allotment thanks to the 25-video library cap and 30-day temp-file retention.

Common confusion points

"My broadcast sent but the count is lower than I expected" Either the cap kicked in (you got the 500 warning at send), or some recipients failed validation (malformed emails are silently dropped by the parser). The response surfaces both numbers -- check the toast for "X sent, Y failed".
"The recipient says the video link doesn't work" Most likely: the file was pruned by the 30-day cleanup (no recent plays, no deep watch) — the landing page returns a tasteful "Video Expired" message. Re-record and re-send.
"Folder organization for the library?" The schema + server endpoints for folders shipped in Phase 2; the UI lands in a future pass. Until then the library is a flat list of 25 max, ordered newest-first.
Open the Video Library

Meeting Recordings

Meeting Recordings is a unified library of every Google Meet and Microsoft Teams recording you've been part of, auto-filed to the matching CRM contact by attendee email. The video files stay in your Google Drive (for Meet) or OneDrive / SharePoint (for Teams) — the CRM stores the metadata + transcript + a fresh signed URL on demand. No third-party recorder (Gong, Otter, Chorus) needed; the integration just imports what your meeting platform already records.

Plan requirements. Google Meet recording requires a paid Google Workspace plan (Business Starter or higher); free Gmail accounts can't record Meets. Microsoft Teams recording requires a Microsoft 365 Business or Enterprise license that includes meeting recordings. CrawlSpace pulls finished recordings — it doesn't record the call itself, so meetings still need to be recorded the normal way (toggle on in Meet / Teams). Transcripts depend on your provider transcribing and successfully generating one.

Connecting a provider

Open Settings → Integrations → Meeting Recordings. Click Connect Google Workspace or Connect Microsoft 365. Standard OAuth flow grants the CRM read-only access to:

  • Googlemeetings.space.readonly (list recordings + transcripts), drive.readonly (resolve the MP4 file by id).
  • MicrosoftOnlineMeetingRecording.Read.All + OnlineMeetingTranscript.Read.All + Files.ReadWrite for the Drive resolve (Microsoft Graph requires the broader scope set).

Connection tokens are stored encrypted on our side. Only the connection owner can disconnect — the pulled recordings + transcripts stay visible to the whole org so a coverage rep can find a former teammate's calls.

How the sync works

Every 15 minutes the CRM checks each connected Google / Microsoft account for new recordings since the last sync. For each one it finds:

  1. Pulls the recording's metadata (start/end time, attendee list, file location) plus the transcript when one is available.
  2. Matches attendee emails to your CRM contacts (only your contacts — never a teammate's).
  3. Files the recording against every matching contact. Re-running the sync within the same 15-minute window is safe; it won't create duplicates.
If you ever see "Reconnect Microsoft" right after a sync, give it a few minutes — sometimes two sync passes race for the same account and one transiently fails. The next pass uses the fresh connection and clears the warning automatically.

Auto-attachment by attendee email

Each recording's attendee list is matched against your CRM contacts by email. A meeting with you and two CRM contacts auto-attaches to both contacts — open either contact and the Meeting Recordings card lists all their recordings, newest first. External attendees (no matching contact) are listed by name + email but don't get auto-attached.

Why playback links rotate

The CRM never stores the video file itself. When you click Play, the CRM generates a fresh playback link to your Google Drive / OneDrive / SharePoint file that's valid for about an hour. This keeps the file in your own storage (no duplication, no extra storage cost on your CrawlSpace bill) and prevents anyone from hot-linking the file outside the app.

Public share links

Click Share on any recording to create a public link. Anyone with the link can view the recording + transcript without a CrawlSpace login. Set an optional expiry, and revoke any time — the link stops working immediately.

AI summary

Click Summarize on any recording with a transcript. The CRM passes the transcript (truncated to 200k chars to keep API costs predictable) to Claude Haiku 4.5 and stores the response. Idempotent — re-clicking shows the cached summary unless you check Re-generate. Summary includes a paragraph overview + a bulleted action-items list with suggested assignees (the rep creates the actual tasks manually; auto-creation is on the roadmap).

Transcript search across the org

The search bar at the top of the Meetings tab matches against transcripts org-wide. Find every call where "pricing", "renewal", "competitor", or any objection phrase came up. Real-time picture of which deals are at risk + a coaching feedback loop for the whole team.

Connection status

The header strip shows a per-provider chip:

  • green ok — last sync succeeded.
  • yellow "Reconnect needed" — your connection was revoked (admin pulled the app, password changed, etc.). Click Reconnect.
  • orange admin_consent_required — Microsoft 365 only; an admin needs to approve the app for the tenant before the user-level token works.
  • red error — the most recent sync failed. Hover to see the last error message; click Reconnect or wait for the next 15-minute sync to retry.

What's deferred

  • Zoom recordings — no API integration today. If Zoom is your primary platform, use the Drive / OneDrive export from Zoom's cloud recordings and attach manually for now.
  • Real-time transcription — transcripts come from the provider post-call, not from CrawlSpace mid-call. Use Meet / Teams native transcription to enable.
  • Auto-create tasks from action items — the AI summary lists action items with suggested assignees; the rep creates the matching Tasks manually. Auto-creation is on the roadmap.
  • Speaker diarization beyond provider output — we use whatever the provider transcribes. Meet labels speakers by attendee; Teams does the same. No additional ML reprocessing.

Common confusion points

"My meeting recorded but it's not showing up in CrawlSpace" Check three things: (1) is your account on a plan that supports recording (free Gmail / personal Microsoft don't)? (2) did the meeting actually record — does the file exist in your Drive / OneDrive? (3) has 15 minutes passed since the meeting ended? The sync runs every 15 minutes, and the provider also takes a few minutes to finalize the file after the call ends.
"My Microsoft connection keeps saying it needs reconnecting" Occasionally two sync passes race on the same connection and one transiently fails. If you see persistent "reconnect needed" warnings after a few minutes, your Microsoft connection was actually revoked (admin pulled the app, password changed, etc.) — click Reconnect.
"I can't find a transcript on a meeting that definitely had captions on" Captions and recorded transcripts are different. Meet generates a transcript only when you turn on transcription (not just captions) AND the recording is enabled. Teams transcribes most recorded calls but the file isn't immediately available — it can take up to 30 minutes after the meeting ends for the transcript to finalize.
Open the Meeting Recordings tab

Jobs & Projects

Jobs and Internal Projects are two sidebar tabs pointing at the same underlying project-management feature, filtered by project_type: Jobs = customer-facing service work (linked to a CRM contact + deal); Internal Projects = internal team initiatives with no customer attached. Same boards, items, sprints, custom fields, and views — different audience.

Creating a project

  1. Click + New Job or + New Project depending on which tab you're on. The created project carries the right project_type automatically.
  2. Required: name. Optional: description, color, customer link (for Jobs), start/end dates, priority, project lead.
  3. Date sanity check: if end < start, you'll get a confirm dialog ("Target end date is earlier than the start date. Continue anyway?") — sometimes you legitimately mean "delivered last quarter," so it's a warn-not-block.
  4. On create, the CRM seeds a default column set (Backlog / To Do / In Progress / Done) which you can edit in Project Settings.

Five views per project

  • Board — kanban with drag-and-drop between columns. Default view. Group By dropdown swaps the column dimension between status / assignee / priority / type / sprint.
  • List — spreadsheet-style table with sortable columns. Faster for 50+ items.
  • Timeline — gantt-style bars per item's start-to-due range with a red "today" line. Items without dates don't appear.
  • Calendar — monthly grid with items plotted on due date, color-coded by status.
  • Dashboard — stat cards (Total / Completed / Overdue / Progress % / Unassigned), status breakdown chart, item-type counts, recently completed list.

Items, subtasks, comments

Items have: title, description, type (Task / Bug / Feature / Software-parent / Milestone), priority (Urgent / High / Medium / Low), assignee, due date, labels, position, effort (story points), and an auto-incrementing item number per project (e.g. #42).

Subtasks are checklist children of an item — flat list, one level deep. Subtask completion count shows on the kanban card (e.g. "2/5"). Moving the parent to a "done" status auto-completes its subtasks.

Comments support @mentions (notifications fire to the mentioned user), are HTML-escaped before render, and URLs auto-linkify.

Watchers + notifications

Click Watch on any item to subscribe to its updates. Watchers receive in-CRM notifications when comments are added, status changes, or @mentions land. The project header bell icon shows all your project-scoped notifications — mark individually or all-at-once.

Sprints + custom fields + automations

  • Sprints — time-boxed buckets with name, goal, start, end. Assign items to a sprint, filter the board by sprint to focus on the current iteration.
  • Custom fields — per-project: text, number, date, dropdown, checkbox, URL, email, or formula. Appear in the item-detail sidebar.
  • Status automations — per-column rules: auto-assign a user, auto-set a due-date offset, auto-notify someone when a card enters a column.
  • Recurring items — set a recurrence rule (daily/weekly/monthly); completion materializes the next occurrence.

Quote → Job auto-create

When a lead converts to a customer (closed-won) the CRM can auto-create a Job from a template — columns, items, and subtasks pre-built, linked to the customer + deal contract value. Set up in Project Settings → Templates. Skips the "what did we agree to again?" kickoff conversation.

Cross-project view

The My Work view (sidebar nav, under Outreach) shows every item assigned to you across every project. Filter by Open / Overdue / Due This Week / Completed. Group by project, priority, status, or due date. Your morning starts in one place, not twelve project tabs.

Time-entry validation

When the time-tracking UI ships, the underlying _pmLogTime helper enforces: hours must be positive, and a single entry can't exceed 24 hours (longer entries are almost certainly a forgotten timer or typo like "210" meant "2.10"). The hard cap protects burndown / invoice totals from nonsense data the moment the UI ships.

What's deferred

Several project-management features are in flight — the data plumbing is in place but the item-detail UI ships together in an upcoming release:
  • Time tracking — Estimate / Actual hours per item with Dashboard rollup. The Dashboard "Hours Logged" stat card already exists; it stays at 0 until the in-item log-time UI ships. Reports can already filter on logged + estimate hours.
  • Dependencies — "Blocked by" / "Blocks" relationships between items. Coming in the next iteration.
  • File attachments — URL or native upload on items. Coming in the next iteration.
  • Hours → invoice line items — once time tracking ships, logged hours will flow into draft invoice line items on the linked customer (Jobs only).

Common confusion points

"Two reps dragged the same card to different columns and one move got lost" Last-write-wins on kanban drag — there's no optimistic-locking guard. In practice this is rare (two reps dragging the same card within the same second) and the cost of a "conflict, please refresh" error message would be worse UX than just letting the second drag win. Refresh the board to see the final state.
"My Hours Logged stat says 0 but I created items with estimate hours set" The Hours Logged stat sums actual logged hours, not estimates. Logged hours need the time-tracking UI which is on the roadmap. Estimate hours don't have an input field yet either — both are visible to the Report Builder but neither has a UI input today.
"My Internal Project should have a customer but I don't see the Link to Customer field" Internal Projects intentionally hide the customer field. Use a Job (customer-facing) if the work is tied to a paying customer. You can't convert an Internal Project to a Job after creation — make the type call up front.
Open the Jobs tab

Inventory

Inventory is the products + services catalog (pricebook) that feeds Quotes + Invoices. Add a product once, drop it into any deal as a line item, and let the CRM track stock + revenue + margin from quote through invoice paid. Supports physical products, services, and recurring subscriptions in the same catalog. Single quantity-on-hand per product — multi-warehouse / multi-location is not supported.

Product types

  • One-Time — service or physical good charged once when sold. Optional fields: cost, vendor, unit of measure (each / hour / day / sq ft / linear ft / cubic ft / lb / oz / gal), track-inventory toggle, quantity on hand, low-stock threshold.
  • Recurring — subscription billed on a weekly / monthly / quarterly / annually cadence. Optional fields: setup fee, trial-period days, minimum-contract months, auto-renews toggle. The CRM stores the structure but doesn't auto-bill (that's a Stripe/Square responsibility — see Sync below).

When stock deducts

Per product you pick when stock decrements:

  • Sold (default) — when the linked deal closes won.
  • Quoted — when a line item is added to a quote (reserves stock optimistically). Risky if you quote heavily without closing — use only when stock is committed at quote time.
  • Invoice paid — when the linked invoice is marked paid (automatically via Square / Stripe OR manually via Mark Paid). Matches "stock leaves the warehouse when money arrives" — best for physical retail.
  • Manual — never auto-deducts; the rep clicks Adjust on the product card to record the change.

Concurrency-safe stock math

Stock decrements are race-condition-proof. If two reps both close deals for the same product at the same moment, both deductions land correctly — stock won't show "10" when both deals should have pulled it down to "8". (The old behavior could leave a deduction unaccounted for if writes overlapped exactly.) The system retries on its own if needed; you don't see anything change in the UI.

No double-deduction on invoice-paid

Every deduction is recorded in a ledger before applying. If the same invoice triggers the deduction twice (e.g. a manual Mark Paid plus a delayed Square notification for the same invoice arriving moments later), the second attempt detects the existing ledger entry and skips — guaranteeing one deduction per invoice no matter how many times the trigger fires.

Adjustments ledger

Every stock change writes a row to inventory_adjustments: the product ID, delta, before/after values, source (invoice_paid / closed_won / manual / csv_import), and a related invoice ID when applicable. The product detail panel shows the full history. Use the Report Builder to roll up "stock movement by SKU last 30 days" or "manual adjustments by user."

Low-stock notifications

When stock crosses (down through) the per-product threshold, the CRM fires a notification + tags the product with a red "Low Stock" badge in the catalog grid. The notification is informational only — there's no PO auto-creation or vendor auto-email yet (see Deferred below).

CSV import

Bulk-load via the Import CSV button. 10 MB file cap. Auto-detects column-name variations (e.g. "Product Name" / "Item" / "Title" → Name; "Item Code" / "Part Number" → SKU; "Wholesale" / "Cost" → Cost; "Reorder Point" / "Min Quantity" → Low Stock Threshold). Smart type coercion: $1,299.95 → 1299.95; yes/no/true/false/1/0/x/on/off → boolean.

Upsert by SKU — toggle this before importing so re-imports update existing products instead of creating duplicates. The import now also does within-file SKU dedup: if your CSV itself has two rows with the same SKU (paste error, duplicate sheet rows), the last occurrence wins. Previously both rows landed as separate products with no DB constraint to catch the duplicate.

Stripe + Square sync

  • Stripe — two-way sync of product + price. Edits in CrawlSpace push to Stripe instantly; new products in Stripe pull back on the periodic sync.
  • Square — one-time catalog import + ongoing stock-count sync. Square is the source of truth for stock; CrawlSpace pulls the current count on every sync.
Sync direction matters: if Stripe is your source of truth for price, edit price in Stripe and let the sync refresh CrawlSpace. If CrawlSpace is your source of truth, edit in CrawlSpace and the push fires immediately. Don't edit both at once — last write wins.

Closed-won workflow

Marking a lead as Closed Won flips all "quoted" line items to "sold", updates the contact's Amount field to the sum of sold items, and decrements stock for products with deduct_on='sold'. Reopening a Closed Won deal (status change away from Closed Won) prompts: "This contact has N sold items. Reopen them and restore inventory?" Confirm to flip sold → quoted + restore stock + revert the Amount field. Cancel to keep items as sold (you'd do this if you closed a different deal but want to track the lost one separately).

Line items on quotes

Open any contact → scroll to Quote Items → click + Add Product. Picker shows active products only; inactive products stay available on past quotes but don't clutter the picker. Per line item edit: quantity, unit price override (without changing the master product), discount %. Totals + the contact's Quote Amount update inline with a half-second debounce.

Product image upload

5 MB cap per image. JPEG / PNG / GIF / WEBP only — SVG is rejected because SVGs can carry inline a piece of malicious code that runs when the image is fetched directly. Images are stored under your org's private space, so another org can't pull them even if they had the URL.

Margin reporting

Set Cost on each product to enable margin math. The Report Builder exposes price, cost, and a computed margin = price - cost column for any custom report. No FIFO / LIFO / weighted-average cost accounting yet — Cost is the value at the moment of report run, not the historical cost at sale time.

What's deferred

Several inventory features are common in dedicated inventory systems (NetSuite, Cin7, etc.) but deliberately out of scope today:
  • Multi-location / multi-warehouse — single quantity_on_hand per product. If you have multiple physical locations and need per-location stock, a dedicated WMS is a better fit.
  • Lot / serial tracking — products are interchangeable units; no lot numbers or expiration dates.
  • Reorder-point automation — low-stock crosses fire a notification only. No PO auto-creation, no vendor auto-email.
  • Inventory valuation reports (FIFO/LIFO/weighted) — margin uses current Cost, not historical cost-at-sale.

Common confusion points

"My stock went down by 1 when two reps both closed deals for this product" Old behavior. Both deductions now land correctly even when two reps close deals on the same product at the same moment. If you still see a single deduction, force-refresh so the latest version loads in your browser.
"I imported a CSV and got two products with the same SKU" Two scenarios: (1) within-file duplicates — fixed, last occurrence wins now (a warning notification surfaces the dropped-duplicate count). (2) Re-import without "Upsert by SKU" toggled — creates new rows instead of updating. Always toggle Upsert if you're re-importing a price list from a supplier.
"My SVG product image got rejected" Intentional — SVGs can run inline JavaScript when fetched from the public bucket URL. Convert to PNG or JPEG (most image tools do this in one click) and re-upload.
Open the Inventory tab

Quotes

The Quotes tab is a sortable + filterable inbox of every quote across every customer — distinct from the per-contact line-items workflow inside contact detail, which is great for one-offs. Each quote has its own lifecycle (draft → sent → viewed → accepted / declined / expired / cancelled / converted), a public accept link the customer signs without logging in, and one-click conversion into a real Stripe or Square invoice. Customer-facing branding pulls from your org settings; the audit-trail fields (typed name + IP + timestamp) are captured at acceptance.

Creating a quote

Two entry points:

  • From contact detail — add line items to the contact's Quote Items panel and the quote auto-creates with the next sequential quote number (Q-YYYY-NNNN per-org per-year).
  • From the Quotes tab — click + New Quote, pick a customer, the same line-item editor opens.

Status lifecycle

  • Draft — still building. No public link minted yet.
  • Sent — clicked Send Quote. A 32-char URL-safe token is minted and your Gmail/Outlook compose modal opens with the link pre-filled.
  • Viewed — customer opened the public link at least once. View count bumps on every render.
  • Accepted — customer typed their full legal name + checked the agree box. Acceptance time, signer name, and IP are recorded.
  • Declined — customer clicked Decline (optional reason captured).
  • Expired — auto-flipped once the expiry date passes (whichever comes first: a customer opening it, or the hourly scheduler sweeping).
  • Cancelled — rep clicked Cancel from the editor (terminates the public link).
  • Converted — accepted quote that was Convert-to-Invoiced. Terminal; can no longer be edited.

Accept / decline can't double-fire

If a customer rapidly clicks both Accept and Decline within the same second, only one wins — the other sees an error and bows out. Earlier versions could let both land and leave the quote with both an Accepted AND a Declined stamp. Now exactly one terminal status sticks per quote.

View tracking

Every public-link render writes a row to a per-quote view log (timestamp + IP + user-agent). The editor shows "Opened N times, last on X" with a collapsible per-open table. Useful for "did they read it before declining?" forensics, and for spotting whether the customer forwarded the link (different IP / browser fingerprint on the same quote).

Convert to invoice

Once a customer accepts, the editor surfaces a Convert to Invoice button. One click and the line items snapshot into a real Stripe or Square invoice using whichever payment provider you've connected (Stripe is picked first when both are present). The customer gets an invoice email automatically and the quote flips to Converted with a link to the new invoice.

Double-click guard. A rapid second click while the first conversion is in-flight used to create two real invoices that the customer would then dispute. The button now no-ops on the second click and clears the in-flight flag in a try/finally so it doesn't get stuck on error.
Line-item snapshots. The invoice line items copy from the quote AT THE TIME of conversion, including any per-line discount, quantity, and unit-price overrides. If you edit the product master after the customer accepts, the invoice still reflects what the customer signed off on — no drift.

Generate contract from a quote

Accepted quotes also expose a Generate Contract action. Click it to open the Contract editor pre-filled with the customer + a starter SOW body referencing the quote number. The contract carries a quote_id back so both objects show the linkage in their respective views.

Discount + quantity clamping

Per-line edits — quantity, unit price override, discount % — are debounced (500ms) inline saves. Server-side clamping prevents nonsense values from landing in the DB: discount is clamped to 0-100, quantity and unit price are clamped to ≥ 0. Pre-fix, a tampered DOM or direct DB write could set discount=150 → effective line total goes negative; the convert-to-invoice math then silently clamped the invoice line to $0, losing the rep's pricing intent. The clamp now happens at write time so the quote never holds invalid math.

Public quote URL

The public link lives at /.netlify/functions/quote-public?token=... with a 32-char URL-safe token (unguessable). The page renders the org's name + logo, the line items, the totals, and a typed-name signature block. Customer enters their full legal name, checks the agree box, clicks Sign. No login or account creation required. The same URL stays valid until the quote enters a terminal state, at which point re-visiting it shows the appropriate status banner ("Already accepted by X on Y" / "Declined on Y" / "Expired on Y" / "Cancelled by the sender").

Acceptance certificate

Accepted quotes can mint a printable PDF / HTML acceptance certificate via quote-certificate — captures the signer's name, IP, timestamp, and the full line-item snapshot at acceptance time. Useful for legal / compliance archives or for sending the customer a "thanks for signing" confirmation with a hard copy attached.

Lifecycle scheduler

Every hour the system auto-expires quotes whose expiry date has passed and that haven't already been accepted, declined, or cancelled. The same check also runs the moment a customer opens an expired quote, so the editor and the public page never disagree about whether a quote is still live.

Templates

Save any quote as a template via the Templates button — captures the line items + terms + expiry days + tax / shipping defaults. New quotes can start from a template to skip the from-scratch setup for "standard service package" or "annual-renewal proposal" workflows.

Owner gating

Each quote has an owner_user_id (defaults to whoever created it). A team member who isn't the owner sees the editor in read-only mode with a "you don't own this quote" banner. Owners and org owners / admins can edit / send / convert / cancel. Status-based locks (accepted / declined / converted) apply to everyone — including the owner, since editing a signed quote would invalidate the customer's acceptance.

What's deferred

  • Multi-currency — single currency per org. No per-quote currency override.
  • Tax tables / jurisdiction-based tax — tax is a per-quote text field, not auto-computed by ZIP/jurisdiction. For sales-tax compliance use TaxJar or similar and stamp the computed rate in the tax field.
  • Quote versioning / amendments — each quote is a single document. Editing overwrites; there's no "v2 of Q-2026-0042" thread. If the customer asks for changes after acceptance, cancel + clone is the workflow.
  • Real-time multi-author edits — single-author edits with last-write-wins. Two reps editing the same quote simultaneously will overwrite each other's changes silently.

Common confusion points

"The customer clicked Accept and Decline and the quote shows half-data" Old behavior. Now only one terminal status sticks per quote, no matter how quickly the customer clicks both buttons.
"I clicked Convert twice and got two invoices" Old behavior. The double-click guard now ignores a second click while the first conversion is in-flight. If you somehow still see this, refresh so the latest version loads.
"I entered 150% discount and the customer paid $0" Old behavior. Discount is now clamped to 0–100 at save time; you'll see the input snap back to 100 if you try to enter anything higher. The quote DB never holds an invalid discount value.
"My quote expired but the customer hadn't seen it yet" The expires_at field is hard-deadline — the quote auto-expires whether or not the customer opened it. Set generous expiry windows (30+ days for typical B2B sales cycles) and use the Sent → Viewed status lag to spot quotes that need a nudge.
Open the Quotes tab

Contracts

Contracts is the in-CRM e-signature feature for NDAs, MSAs, SOWs, change orders, and any agreement that needs a signature. Customers sign through a public link — no login, no software install — and the CRM captures a full audit trail (typed name + IP + timestamp + user-agent + the exact body locked at sign time). Multi-recipient sequential signing, inline form fields (signature / initials / text / date / checkbox), reminder cadences, templates, and a one-click handoff from Quotes are all built in.

About enforceability. E-signature legal weight varies by jurisdiction and contract type. CrawlSpace's typed-name + audit-trail capture is built for common U.S. workflows under the federal E-SIGN Act and most state UETA equivalents. For high-value or mission-critical agreements (deeds, wills, court filings), consult your attorney.

Creating a contract

From the Contracts tab click + New Contract, or from a contact detail use the + New Contract button on the Contracts card. Title it, pick the customer ("From CRM" pulls from your leads/customers so {{customer.*}} variables resolve at sign time), set an optional expiry, and write the body in the rich-text editor.

Variables + inline form fields

  • + Insert variable… pulls CRM data live at render time: {{customer.full_name}}, {{org.address}}, {{deal.amount}}, {{today}}.
  • + Insert field… drops a form-field token the recipient fills when signing: Signature (typed name in script font), Initials (boxed), Text, Date, Checkbox. Each field is scoped to a specific recipient — recipient 1 can't accidentally fill recipient 2's slot.

Field tokens render as plain text in the editor ([[signature:customer_sig|1]]). To remove a field, just delete the token. To reassign, edit the number after the pipe (the recipient index) or delete + re-insert.

Sequential multi-recipient signing

A contract can have 1 to N recipients who sign in order. Recipient 2 doesn't see or sign anything until Recipient 1 finishes. Each recipient has a name, email, optional role label ("Customer" / "Witness" / "Counter-signatory"), and (once activated) a unique share_token they receive via email.

Reordering lock. Recipients can be reordered with ↑ / ↓ buttons in the editor — but ONLY before any recipient has been sent. Once the chain starts, the order locks for audit purposes.

Sign atomicity

Rapid double-clicks on Sign (or two browser tabs both hitting Sign at the same moment) can't advance the chain twice anymore. Earlier versions could email the next recipient twice and rotate the link they were about to receive, breaking the first email. Now exactly one sign wins and the second click gets a "you've already responded" message.

Public sign URL — per-recipient tokens

Each recipient gets a unique 32-char URL-safe share_token stored in contract_recipients.share_token. The public URL is /c/<token>. There's also a legacy contracts.share_token field for single-signer contracts that pre-date the multi-recipient flow.

Legacy fallback now gated. Pre-fix, anyone who somehow learned the contract-level share_token on a multi-recipient contract could sign as a "legacy" signer, bypassing the per-recipient gate. The resolver now rejects the legacy path when any contract_recipients rows exist for that contract — multi-recipient contracts MUST be accessed via a per-recipient token.

Objections vs declines

  • Raise concerns — recipient types an objection, chain PAUSES, you get a notification. You revise + click Resend on that recipient (fresh token + status reset) and the chain resumes.
  • Decline — ends the contract entirely. Status flips to declined; all remaining recipients' tokens are invalidated. No recovery — create a new contract.

Open tracking

Every public-link render logs to contract_views with IP + user-agent + referer. The editor shows "Opened N times, last on X" plus a collapsible per-open log so you can spot "did their lawyer open it from a different IP?" forensics before signing.

Templates

Save any contract as a reusable template via the Templates button. Templates store body + default title + default expiry days + variable + field tokens. New contracts pick a template from the "Start from template" dropdown and inherit its structure as an editable draft.

Reminder cadence

The system automatically nudges pending recipients on a configurable cadence (typical default: day 3, day 7, day 14 after the last touch). Disable per-contract via the Auto Reminders toggle in the editor. The reminder email goes through the contract owner's connected Gmail / Outlook so it looks like a personal follow-up, not a robot.

Lifecycle scheduler

Runs hourly: auto-expires past-due contracts that aren't in a terminal state, fires the webhook to integrated systems on full execution, and handles edge cases where the in-flight chain advance failed and needs a retry.

Certificate PDF (audit trail)

The contract-certificate endpoint spins up headless Chromium, navigates to the public contract page with a ?print=1 flag (which suppresses the sticky sign bar + success overlay), and captures the rendered page as a PDF. Pixel-perfect parity with what the recipient signed — same fonts, same paper background, same signature rendering, no duplicate renderers to maintain.

Cert auth. The cert endpoint accepts either a per-recipient ?token= (self-authenticating) or a ?id=<contract-uuid> from the CRM-side button. The ?id= path now REQUIRES a Bearer token tied to a member of the contract's org — pre-fix, anyone who learned a contract UUID (from logs, Slack, git history) could pull the signed cert + audit trail. Both CRM-side callers (Signed PDF + Draft PDF buttons) now use authenticatedFetch + blob download.

Quote → Contract handoff

Accepted quotes have a Generate Contract button that opens the contract editor pre-filled with the customer + a starter SOW body referencing the quote number. The contract carries a quote_id back so both objects show the linkage in their respective views.

What's deferred

Features that DocuSign / Adobe Sign / similar enterprise e-sign tools have, but CrawlSpace intentionally doesn't:
  • KBA (knowledge-based authentication) — no SSN/credit-history identity questions before signing. Fine for typical B2B contracts; required by some regulated industries (real-estate closings, certain financial agreements).
  • Notarization workflow — no remote online notary (RON) integration. For notarized docs use a dedicated RON service.
  • Bulk send — no "send this contract to 200 customers in one batch" workflow. Each contract is a one-off.
  • Branching / conditional fields — fields are always shown / always required for their assigned recipient. No "if checkbox A is ticked, show field B" logic.
  • Native mobile signing app — signing works in mobile browser via the public URL (responsive). No dedicated iOS / Android app.

Common confusion points

"I clicked Sign and the next recipient got two emails" Old behavior. Rapid double-clicks on Sign now advance the chain exactly once. If you still see this, force-refresh so the latest version loads in your browser.
"My Signed PDF button stopped working after the security update" The button now requires you to be signed in to the CRM. If you opened the editor in an incognito tab without logging in, it'll fail. Log in normally and the button works as before.
"A customer says my contract isn't enforceable" E-signature enforceability varies by jurisdiction and contract type. The CRM captures the standard E-SIGN Act audit trail (typed name + IP + timestamp + user-agent + body lock) which is sufficient for most U.S. B2B / B2C contracts. For high-stakes or specialized agreements (real-estate deeds, wills, certain regulated industries), consult your attorney about whether you need notarization or KBA on top.
Open the Contracts tab

Invoicing (Financial tab)

The Financial tab is a Stripe + Square passthrough. The CRM stores provider_invoice_id references and reflects status via webhooks — money flows from the customer directly to your Stripe / Square balance. Zero CrawlSpace markup, zero funds held server-side. Connect either processor (or both — invoices pick Stripe first when both are connected, matching the Quote → Invoice convert path).

Connecting a processor

Only org owners and admins can connect or disconnect a payment provider. Standard OAuth flow: click Connect Stripe or Connect Square, authorize on the processor's page, and the encrypted token is stored server-side. Once connected, every team member with Financial = Edit on their permission profile can create invoices using that connection.

Creating an invoice

  • From a contact — open the contact, click + Create Invoice on the Financial card. Line items pre-fill if the contact has Quote Items.
  • From a Quote — Convert to Invoice on an accepted quote (one-click; line items snapshot from the quote, double-click guarded).
  • From the Financial tab — generic + Create button, pick a customer.
  • Manually as paid — for cash / advance / wire that didn't flow through Stripe or Square, click Mark Paid in Cash/Advance on the invoice modal. Records the invoice locally + appends the payment to the contact's revenue timeline without touching the processor. Org owner/admin only.

Settings (per org)

  • Payment terms — Due on receipt / Net 7 / 15 / 30 / 60 / 90.
  • Default note — appears on every invoice.
  • Late fee — % of unpaid total, applied after a grace-period window when the lifecycle scheduler runs.
  • Customer email — subject line + branded HTML body with placeholders. Sent via your connected Gmail/Outlook account so the customer sees a real reply-to address, not a no-reply.

Forged-event protection

Both Stripe and Square cryptographically sign every payment event before sending it to the CRM. The CRM verifies the signature before acting on the event, so a forged "this invoice was paid" notification from anyone else is rejected automatically.

No double-counting. Payment providers occasionally re-deliver the same event (especially during outages). The CRM checks each payment against the contact's existing revenue history before adding it, so a re-delivered "paid" event won't double the customer's lifetime value or deduct stock twice.

Mark-paid permissions + double-submit guard

Mark-paid is a money-side action — it triggers revenue updates, deal advancement to Won, stock deduction, and lead auto-conversion. Two guards protect it:

  • Role gate — only owners and admins can mark an invoice paid. Members with hidden Financial access can't trigger it even by working around the UI.
  • In-flight guard — the confirm dialog disables the button while the action runs, so a rapid second click can't create two duplicate invoice rows.

Late-fee + reminder automation

Every hour, the CRM sweeps overdue invoices: applies your configured late fee after the grace period passes, sends overdue reminder emails through the invoice creator's connected mailbox, and updates the dashboard's "overdue" panel. You don't have to babysit anything — set the late-fee + grace period on the invoice and forget about it.

Recurring invoices

Products with a recurring billing interval (set in the Inventory tab) generate a fresh invoice each period automatically. If you're using Stripe or Square subscriptions, the provider does the actual billing; otherwise the CRM creates the invoice ready for you to send.

Pagination on the invoice list

The Financial tab's invoice list supports pagination, up to 500 invoices per page, with a total count shown ("showing 1–200 of 847"). If your org has a long invoice history, use the page controls at the bottom to navigate beyond the first batch.

Sync from Square

The ↻ Sync from Square button pulls in invoices created directly in Square's dashboard (outside the CRM) and matches them to your CRM contacts where possible. Use this when you started with Square and want historical invoices visible inside the CRM. There's no equivalent Stripe sync — the Stripe webhook handles drift naturally because every Stripe invoice fires an event we capture.

What's deferred

  • Multi-currency per invoice — single currency per org, inherited from the Stripe / Square account. No per-invoice currency override.
  • Inline tax computation by ZIP / jurisdiction — tax is a numeric field on the invoice. For sales-tax compliance use TaxJar or similar and stamp the computed rate.
  • White-label invoice URLs — payment pages are Stripe-hosted / Square-hosted. The customer's email is branded but the payment page itself is the processor's.
  • Crypto / ACH / wire orchestration — whatever payment methods Stripe / Square accept in your processor account are what customers see. The CRM doesn't add or restrict methods.

Common confusion points

"The customer paid $500 but their lifetime revenue shows $1,000" This was the pre-idempotency-fix behavior. Both Stripe and Square retry webhooks; the second delivery used to re-append the same payment to the revenue timeline, double-counting on the re-sum. Now fixed. If you see an old double-count from before the fix shipped, open the contact, edit the revenue_timeline to remove the duplicate entry, and re-save (or contact support).
"My team member got a 403 trying to mark an invoice paid" Mark-paid is now restricted to org owner and admin roles. If a regular member needs to record cash payments, bump their role to admin (Settings → Organization → Members) or have the owner mark it for them.
"Our org has 250 invoices but only the first 200 show in the list" Pre-pagination behavior. The endpoint now accepts offset+limit and the UI shows a "Load more" or paged view of the full history.
Open the Financial tab

Document Library

The Document Library is centralized file storage for everything the team reuses or needs to share — proposals, contracts, one-pagers, pricing sheets, case studies, photos, brand assets. Lives in the Library sidebar group alongside Meeting Recordings. Org-shared by default (Team scope) so a coverage rep can find a former teammate's files; switch to Mine scope for just-your-uploads view.

Upload

Click Upload and pick any file (no file-type restriction — upload anything). The file lands in your active folder (or the root if no folder is selected). PDF + image previews render inline in the detail panel; other formats (Word, Excel, PowerPoint, video) download for opening locally.

Storage-clean uploads. If the rare metadata save fails after the file has already uploaded, the file is cleaned up automatically so nothing orphans into your storage allotment.

Auto-categorization

On upload, the CRM picks a category from the file name (case-insensitive substring match):

  • proposalproposal
  • contract or agreementcontract
  • brochurebrochure
  • pricing or quotepricing
  • everything else → other

Override the category any time from the document detail. Manual overrides are sticky — re-running text extraction doesn't reset them.

Folders

Build a folder hierarchy via the tree on the left. Create / rename / move folders, drag-and-drop documents in and out. Cycle prevention stops you from accidentally moving a folder into one of its own children (would otherwise create an infinite loop in the tree renderer).

Search

Two layers run together: full-text search inside PDFs, Word docs, and plain text (works on files with a real text layer — not scanned-image PDFs without OCR), plus a name-match fallback so you always get something even while content extraction is still running on a fresh upload.

Search spans your whole accessible library — folder filters are temporarily ignored while searching so you don't miss a hit in a different folder.

Filters + sort

  • Scope — Mine (just yours) or Team (everything in your org you have access to).
  • Folder — current folder or root (cleared during a search).
  • Category — pick one of the auto-detected categories.
  • Starred — toggle to show only starred docs.
  • Contact / Job / Quote — filter to docs linked to a specific record.
  • Sort — date / name / size, asc or desc.

Pagination

The list shows up to 500 documents per page. Orgs with thousands of documents can page through the full library instead of hitting an arbitrary cap.

Public share links

Click Share on any document to mint a public URL with an unguessable random token. Default expiry is 30 days (max 365). Anyone with the link can preview / download without a CrawlSpace login. Revoke any time — the link stops working immediately.

For up to about 5 minutes after a user clicks the share link, the file may stay downloadable for that specific user even after you revoke — the one-time download key they grabbed is valid for that short window. After it expires, no further downloads are possible. This is standard short-lived-link behavior.

Link to records

Set a document's "Linked to" dropdown to a contact, job, or quote and the file shows up on that record's detail panel. Useful for "show me every photo attached to the Acme job" — one filter, every relevant file.

Bulk operations

  • Bulk move — multi-select then pick a target folder.
  • Bulk delete — multi-select and confirm; the file and the metadata are both removed.

Attach from library (in email composer)

The email compose modal has a From Doc Library button. Browse / search / star / multi-select to pick docs, click Attach. The CRM grabs the file at attach time and ships it inside the outgoing email. Critically: this is a snapshot, not a link — deleting the doc later doesn't break previously-sent emails. The recipient's saved copy is unaffected.

Text extraction

Runs automatically on upload to power the inside-the-file search. Re-triggering on the same doc is safe (no double-processing). If extraction fails on a particular file (encrypted PDF, corrupt file, unsupported format), the file is still downloadable and searchable by name — you just won't get inside-the-file content search for it until you replace the file with a clean copy.

What's deferred

Several "polish" features marketed on the solutions page are still in flight:
  • Inline preview for Office / spreadsheet files — only PDF + images render inline today; Word/Excel/PowerPoint download for local opening.
  • Automatic version history per template — uploading a new file with the same name creates a new document, not a version. Use name suffixes ("MSA v2") for now.
  • Per-file audit panel (previews + downloads + shares + deletes) — the dedicated per-file activity log + CSV export + access alerts land in the next iteration. Today the org activity log captures broad actions but there's no per-file drill-down.
  • OCR for scanned-image PDFs — text extraction works on text-layer PDFs only. Scanned receipts/contracts aren't searchable by content.

Common confusion points

"I uploaded a scanned PDF and search can't find words inside it" Inside-the-file search needs a real text layer. Scanned-image PDFs have no text layer until you OCR them externally (Adobe Acrobat's "Make Searchable" feature is the easiest). Re-upload the OCR'd version and content search will pick it up.
"My .docx attached fine but the preview just downloads" Intentional — Word/Excel/PowerPoint preview is deferred. They download to the system's default handler so the rep can open in Office / Pages / Google Docs.
"I deleted a doc but the email I sent last week still has the attachment" The attachment is a snapshot of the file at send time, not a live link. Deleting the doc from your library doesn't pull it out of emails you already sent. The recipient's copy is unaffected.
Open the Document Library

Reports

The Reports tab is a read-only viewing hub for every report — 27+ pre-built templates AND your custom reports — organized in collapsible folders. Building / editing happens in the separate Report Builder tab (Salesforce-style split: consuming vs building are different mental modes). Every report opens to a full-width chart + table viewer with Export CSV / Export to Sheet / Print-to-PDF.

Folder organization

Reports group by folder. Custom folders (My Reports + any user-created) sit at the top, then the seven built-in category folders: Sales & Pipeline, Lead Performance, Activity, Customers, Marketing, Project Management, Products. Each folder shows a count badge and is collapsed by default. Click + New Folder in the Reports header to create a custom folder; pick the "Move to..." dropdown on any custom report to relocate it. Built-in template reports stay in their original category — they can't be moved.

27+ canned reports

  • Sales & Pipeline — Pipeline Value, Status Breakdown, Win/Loss, Revenue by Month, Conversion Funnel, Deal Aging
  • Lead Performance — Source, Owner, Territory, Tag
  • Activity — Status × Source, Owner Leaderboard, Sequence Performance
  • Customers — Customer Revenue, Health, Upcoming Renewals, Check-in Cadence, Cohort Analysis, Sales Velocity Modeling, Win Probability Scoring
  • Marketing — Newsletter performance, Drip Campaign performance
  • Project Management — Items by Status, Workload, Items by Type, Overdue, Cycle Time, Bug Tracker, Story Points, Project Completion (+ Time Tracking when that feature ships)
  • Products — Inventory

Viewer layout

Click any report row to open the full-width viewer. Chart on top (when a Group By is set), data table below. Toolbar buttons:

  • Edit — opens in Report Builder pre-filled with the config.
  • Chart type — dropdown in the chart header (Auto / Bar / Line / Pie / Table only). Auto picks the best fit based on data shape.
  • Export CSV — downloads the current view (respecting filters).
  • Export to Sheet — creates a new tab in your connected Google Sheet / Excel with the report data; tab named with the report + date for snapshot history.
  • Print / PDF — pops a clean popup, captures Chart.js charts as static images, triggers your browser's print dialog. UI chrome (sidebar, headers) excluded.

Grouped detail view

Summary reports show group headers (with subtotals underneath) and individual rows under each group — Salesforce-style "both aggregate AND detail in one view." Click a group header to collapse / expand its rows.

Search + deep links

Use the search box in the Reports header to filter across every report by name, description, or category. Matches in any folder. Folders without matches collapse out of the way. Every report has a unique URL like #reports/pipeline-value — bookmark, share with a teammate, or open in a new tab; the report opens directly to the viewer.

Custom icons + colors

When saving a custom report, pick from 18 icons (Dollar, Chart, Trophy, Trend, Target, User, Map, Tag, List, Bolt, Mail, Drop, Users, Clock, Timer, Bug, Rocket, Box) and any color from the picker. The icon + color appear in the gallery row for quick visual scanning.

Permissions

Reports tab access is gated by Permission Profiles (Settings → Organization → Permission Profiles): View can open + view but not edit; Edit can create + modify; Hide removes the tab from the sidebar entirely. Custom reports also respect the org boundary — a member from another org can't see your reports, regardless of how they try to reach them.

Open the Reports tab

Report Builder

The Report Builder tab is where you create + edit custom reports. Salesforce-style layout: left panel has Outline + Filters sub-tabs; right panel has the chart on top + a data preview below. Live preview updates as you change settings.

Source objects (what you can report on)

Nine source objects in the SOURCE_OBJECTS registry — each has its own field set with per-field aggregations, filterability, and groupability flags:

  • Contacts (Leads + Customers) — storage-mode-aware
  • Quote Line Items
  • Sequence Sends — one row per email queued / sent through a sequence (powers "sends per sequence per month")
  • Email Events — opens / clicks / replies on any tracked send
  • Products / Inventory
  • Newsletters
  • Drip Campaigns
  • Marketing Sends — unified send table across newsletters + drips
  • Project / Job Items

Multi-object joins (Phase 2)

Join related sources via foreign keys registered in the JOIN_REGISTRY: Contacts ↔ Quote Line Items, Contacts ↔ Project Items (via contact_id), etc. Joins use LEFT-JOIN semantics so parent rows without children still appear.

Cross-product safety cap. A 1:N join flattens into one output row per (parent, child) pair. A contact with 10,000 line items would expand to 10,000 rows in memory before the LIMIT clip — at multi-join depth (Contact × Line Items × Notes) the combinatorial explosion could spike browser memory into the hundreds of MB and freeze the tab. The flatten now caps at 10× the requested limit with a clear notification: "Report truncated at N,000 rows (cross-product explosion). Tighten filters for complete results." Pre-fix, big joins silently locked the page.

Outline tab

  • Report Name (required), Description, Folder.
  • Report On — pick the source object.
  • Columns to Show — defaults pre-selected based on the source object. Override by checking / unchecking; reorder with ↑ / ↓ arrows.
  • Group By (optional, but the KEY decision) — empty = flat list of records; set = report becomes a Summary with totals + a chart.
  • Show Top N + Sort + Limit.

Filters tab

Tab label shows the active filter count (e.g. "Filters (3)"). Use AND / OR toggles to combine filters. Click + Add Filter — the UI adapts to field type:

  • Date fields → date picker + relative date presets ("Last 30 Days", "YTD", "This Quarter"). Presets are stored as tokens (last_30_days) so the report stays current as time passes.
  • Currency fields → $ prefix on the input.
  • Status / Enum fields → dropdown of existing values pulled from the data.

Values to Calculate (Summary reports only)

When Group By is set, a "Values to Calculate" section appears in a blue highlight box. Add metrics: Count, Sum, Avg, Min, Max — each supports a WHERE clause for conditional aggregation (e.g. "Sum of Amount WHERE status = Won"). The chart auto-adapts to the metrics + data shape.

Date bucketing

The bucketDate function used by Summary aggregation now uses LOCAL time components for every bucket type (day / week / month / quarter / year). Pre-fix it mixed UTC + local — the 'day' bucket called toISOString() while 'week' / 'month' / 'quarter' / 'year' all used getDate() / getMonth() / getFullYear(). For a user near month-end in a negative-UTC timezone, the same timestamp could land in May for the month bucket but June 1 for the day bucket. Now consistent.

Formula fields (Advanced)

Expand the "Advanced: Formula Fields" accordion at the bottom of Outline. Examples: quote_amount - amount (gap to close), (price - cost) / price * 100 (margin %), logged_hours / estimate_hours (effort accuracy ratio). The expression evaluator is whitelist-validated — only arithmetic + parentheses + recognized field names. No function-call syntax, no string concatenation, no SQL injection vector.

Live preview

The right panel updates in real time as you change settings. Data table shows below; chart shows above (when grouped). What you see while building is exactly what your saved report shows. Use this to sanity-check group cardinality + chart fit before saving.

Save flow

Click Save Report. Modal asks for a name (if not already set), an icon (18 options), a color (color picker), and a folder. Saves to the custom-reports list + navigates to the Reports tab with the new report open in the viewer.

Dashboards

Combine multiple reports into a single tile-view dashboard via the Dashboards section. Each tile renders the report's chart + key metric. Order tiles by drag-and-drop. Useful for "morning glance" or "weekly stand-up" dashboards that surface the 4-6 metrics that matter most.

Public sharing

Click Share on any report to mint a token-keyed public URL. The share captures a snapshot at the moment of share creation (frozen rows + chart config), not a live feed — so the public viewer can't pull updated data over time. Revoke any time. The snapshot-not-live design means a leaked share link can't be used to monitor your business in real-time after revocation.

What's deferred

  • Scheduled / emailed reports — no auto-email of report PDFs on a cadence. Run manually + use Print/PDF for now.
  • Pivot tables beyond group-by — single dimension at a time, no row × column pivots.
  • Raw SQL editor — the builder generates the query; no raw SQL escape hatch.
  • ML / predictive analytics — Sales Velocity Modeling and Win Probability Scoring are heuristic / rule-based, not ML-trained.

Common confusion points

"My report shows 'truncated at 10,000 rows' but I have fewer than 1,000 records" The truncation cap is on the CROSS-PRODUCT after multi-object joins — not on your record count. A single contact with 10,000 line items joins into 10,000 output rows. Tighten the join filter (e.g. "Line Items where status = quoted") to bring the flattened total under the cap.
"My month-bucketed report shows different counts than a colleague's" Pre-fix, the bucket function mixed UTC + local time depending on bucket type, so two viewers in different timezones near month-end could see different bucket assignments for the same row. Now consistently local. Force-refresh both browsers so the patched code loads on both ends.
"I shared a report publicly and updated it, but the public link shows old data" Public shares are snapshot-based by design — the rows + chart config are frozen at share-creation time. To publish updated data, revoke the existing share and create a new one (gives you a chance to audit + re-curate what goes out, vs leaking every live edit to whoever has the link).
Open the Report Builder

Activity Log

The Activity Log is the unified audit trail — every meaningful CRM action lands here in chronological order, scoped to the org. Useful for compliance reviews, customer disputes ("when did we last contact them?"), onboarding new team members ("show me everything we've done with this customer"), and forensics ("who marked this invoice paid manually?").

Two views

  • Per-contact — each contact's detail page has an Activity History card scoped to that contact. Type filter + time-range filter built in. This is the view you'll use day-to-day.
  • Org-wide — the Activity Log tab in the Insights sidebar group surfaces every event across every contact, filterable by event type, actor (user), contact, and date range. Built for compliance reviews and quick "who did what" lookups across the whole team.

How the two views stay in sync

Every action writes to both views at once. The per-contact panel is the authoritative source — if a temporary network blip drops the org-wide view's copy of an event, the per-contact panel still shows it correctly. Any subsequent action on that contact catches both views back up.

Net effect: per-contact history is bulletproof; the org-wide tab is best-effort but always catches up next time you act on that contact.

Event types captured

  • Email — sent / received, with direction
  • Call — inbound / outbound, duration, disposition
  • Text — SMS sent / received
  • Sequence — enrollment, step completion, removal
  • Status / Conversion — lead → customer, status changes, statusReason changes
  • Revenue — deal won, invoice paid (manual + Stripe + Square)
  • Document — uploaded, attached to record, shared publicly, deleted
  • Contract — sent, viewed, signed, declined, voided
  • Task — completed, reassigned, snoozed
  • System — automated events (sequence auto-advance, scheduled jobs affecting a contact, etc.)
Logins are NOT captured here. Sign-in events are tracked separately by our auth layer and aren't surfaced in the CRM activity feed today. If you need a login audit, the Settings → Organization → Members table shows each user's "last seen" timestamp, which is the closest in-app proxy.

Filtering + search

The org-wide tab exposes:

  • Event type dropdown — Email / Call / Text / Sequence / Status / etc.
  • Actor — drop down to a specific team member to see only their actions.
  • Contact — type-ahead picker to scope to one customer's history (mirrors the per-contact view but in the tab UI).
  • Date range — uses your browser's local time so the boundary lines up with what you see in the picker.
  • Search — substring match against the entry's details + contact name + actor.

Safe rendering

Email bodies in the detail panel render in a sandboxed preview so any code inside an HTML email can't escape and touch the rest of the CRM. Log text itself is fully sanitized before display so quirky characters in a contact's name or event details never break the UI.

Spreadsheet-mode parallel history

If you're in Spreadsheet storage mode, log entries also append to columns AN onwards on the contact's row in your Google Sheet or Excel file. Format: "YYYY-MM-DD — Contact Name — Type (Direction): Details". Creates a complete history inside your sheet alongside the CRM — useful if you ever leave the product (your audit trail isn't proprietary).

Public share

Click Share on any contact's activity entry (or the contact's full log) to mint a public URL with an unguessable random token. The public viewer serves a snapshot frozen at share-creation time, so revoking the share or editing the entry later doesn't leak through. Set an optional expiry; revoke any time.

Permission boundaries

Activity Log access is gated by the Permission Profile system (Settings → Organization → Permission Profiles). The org-wide tab respects the profile's Activity Log setting (View / Edit / Hide). Per-contact logs always show to anyone who can see the contact. Members of one org can never pull another org's activity — it's blocked at the database level, not just hidden in the UI.

Deletion + immutability

Activity Log entries are append-only from the UI — there's no delete button on individual events. Deleting a contact cascades to their activity entries (clean removal), but individual entries can't be edited or removed once written. This is intentional: the log is the audit trail, and an editable audit trail isn't an audit trail.

What's deferred

  • Login / sign-in events — not surfaced in the CRM activity log today. The Members table's "last seen" timestamp is the closest in-app proxy.
  • Permission-change events — when an admin changes a member's role or profile, the change isn't currently logged to the activity feed. Roadmap.

Common confusion points

"The contact's detail panel shows an email sent yesterday, but it doesn't appear in the org-wide Activity Log tab" A transient network blip can occasionally leave one row visible on the per-contact panel but missing from the org-wide tab. Take any next action on that contact and both views catch back up. No data loss — the row just lands on both views the next time around.
"I want to know when a team member last logged in" Login events aren't in the CRM activity log. Check the user's "last seen" timestamp in Settings → Organization → Members — that's the closest in-app proxy.
"Someone marked an invoice paid manually and I can't tell who" The Activity Log captures the user on every event. Open the org-wide tab, filter to event type = Revenue (or search the invoice number), and the entry shows who marked it. Mark-paid is also restricted to owners and admins, so the list of possible actors is short.
Open the Activity Log tab

Profile

The Profile tab is your personal landing page — distinct from Settings (which is org-wide). Click the user-circle icon at the bottom of the left sidebar to open it. Profile shows your identity, your appearance pick, your connected email account, your subscription (or permission profile, for non-owners), and a sign-out button. The Danger Zone at the bottom is the account deletion entry point.

Identity

  • Avatar — auto-generated from the first two letters of your email, uppercased. No upload UI — the same initials appear next to your name in comments, @mentions, and the activity feed.
  • Display name — what teammates see when you comment or get @mentioned. Edit it from Settings → Profile (Settings, not the Profile tab — legacy split).
  • Email — the email address you signed in with. Changing it is a behind-the- scenes flow; ask support if you need it.
  • Role badge — owner, admin, or member. Owner is unique per org. Admin is assignable.

Appearance — six themes

The theme picker offers six options. Your pick saves to your account and applies instantly across the CRM — no page reload.

  • Original — default, blue accents.
  • Calm — soft, muted palette for long sessions.
  • Dynamic — lighter, vibrant accents.
  • Midnight — deep dark for night work.
  • Editorial Green / Editorial Blue — magazine-style typography with green or blue accents.

Connected email account

Profile shows which provider (Gmail or Outlook) you have connected and the email address the connection was set up with. This row is read-only here — use Settings → Email Integration to connect or disconnect.

Disconnecting also revokes access at Google (Gmail only) Clicking Disconnect Gmail tells Google to stop accepting the connection right away — not just locally. Microsoft doesn't offer the same one-click revoke for Outlook, so the Outlook disconnect message points you at myaccount.microsoft.com → Privacy → Apps if you want to fully remove consent on Microsoft's side too.

Subscription (or permission profile)

  • Owners see plan name, status (active / past due / canceled), next billing date, and amount. The Manage Subscription button opens your billing portal.
  • Members see their permission profile here instead — which tabs are editable, which are read-only, which are hidden. See the Permission Profiles topic in Settings for how those are assigned.

Sign-out — signs you out everywhere

The Sign Out button signs you out across every browser tab, every device, and the mobile app (all on the same account). If you only want to sign out the current tab, close the tab without clicking Sign Out.

Email signature — safety on send

The signature editor (lives in Settings → Email Integration → Signature) is a rich-text editor: formatting, links, and inline images all supported. Two safety nets run on every outbound:

  • Sanitization before send — the signature is scrubbed of any embedded scripts or sketchy code right before the email ships. Defense in depth for the (rare) case of a signature pasted from a site that snuck in a tracking snippet.
  • Image upload allowlist — signature images must be JPG, PNG, GIF, or WebP under 2 MB. Both the file type and the extension are checked, so a renamed file masquerading as an image is rejected.

Danger Zone — account deletion

Scroll to the bottom of Profile. The "Delete My Account" button requires typing your email address exactly — this prevents accidental clicks. Once confirmed, every piece of CRM data tied to your account is removed (contacts, deals, calls, recordings, voicemails, signature images, documents, settings) and your sign-in account itself is deleted.

No undo, no grace period There is no 30-day soft-delete window. Once the confirmation runs, your spreadsheet (if you connected one) stays intact in your Google or Microsoft cloud, and the emails you sent stay in your Gmail / Outlook sent folders — but everything CRM-side is gone.

Common confusion points

"Where do I change my display name?" Settings → Profile, not the Profile tab. The Profile tab is read-mostly — it surfaces your info, but the editable form lives on the Settings page.
"Where's two-factor authentication?" There's no in-app 2FA toggle. If your org needs hardware-key or app-based MFA, enable it on your Google or Microsoft account directly — the sign-in flow respects it automatically.
"I clicked Disconnect Gmail but Google still shows the app connected" Sometimes the revoke call to Google times out (network glitch, token already expired). Visit myaccount.google.com → Security → Third-party apps with account access to confirm and remove manually if needed.
Open the Profile tab

Contact Support

CrawlSpace does not have a chatbot, a help-desk queue, or a self-service ticket portal. The Contact Support button (in Profile, in the sidebar Help menu, and at the top of Settings) opens a modal that emails joshua@crawlspacecrm.com directly through your connected Gmail or Outlook account — you reach a real person, every time.

The form

  • Type — Report a Bug, Feature Request, How-To / Help, Billing Question, or Other. Your choice sets a priority tag on the email subject so the inbox can be triaged at a glance — Bug and How-To are tagged High, Feature Request and Billing are Medium, Other is Low. All five types route to the same person.
  • Subject — optional but recommended. If you fill it in, it becomes the email subject (capped at 140 characters) so the ticket reads "[High] Calendar sync broken on Outlook reconnect" instead of the generic "[High] Report a Bug - CrawlSpace CRM Support".
  • Description — required. Include reproduction steps for bugs, the relevant contact name when applicable, screenshots as attachments.
  • Attachments — up to 5 MB per file, 20 MB combined. Allowed types: JPG / PNG / GIF / WebP screenshots, PDFs, and TXT / CSV. Other file types are dropped from the outbound email — share a link to the file in the description if you need to send something else.

What gets sent with your ticket

  • Your name and email (so support knows who you are).
  • An internal account ID (so support can pull up your data directly without playing 20 questions).
  • Submission timestamp + priority tag.
  • Your chosen type + subject + description + attachments.

No passwords, no connection tokens, no recent error logs are auto-attached. If you want support to see an error message, paste it into the description yourself.

Replies land in your inbox

When support replies, the response arrives in your normal email inbox — no portal login, no separate thread to track. It's just an email reply.

Provider requirement

Tickets ship through YOUR connected email account The form sends through your connected Gmail or Outlook (which is why a copy lands in your Sent folder too). If you haven't connected either yet, the form returns an error pointing you at Settings → Email Integration and tells you the support address so you can email directly as a fallback. Plan ahead — connect your provider before you need to file a ticket.

Privacy + abuse controls

  • Sign-in required — only signed-in users can submit, so the inbox isn't a spam target.
  • Site-locked — the form only accepts submissions from the CrawlSpace site, so an attacker's website can't trick a signed-in tab into firing a ticket on your behalf.
  • File-type allowlist — attachments outside the JPG / PNG / GIF / WebP / PDF / TXT / CSV set are dropped before the email goes out, so a renamed executable doesn't end up in the support inbox.

What this is NOT

  • No live chat. No widget, no bot. The button is a form, the form sends an email.
  • No tier-based SLA. Monthly and annual plans reach the same person at the same address.
  • No 24/7 staffing. Replies target one business day during Eastern-time business hours.
  • No phone support. Email only. If you need a screen-share, ask in the ticket and we'll schedule one.

Common confusion points

"I clicked Send but nothing happened" The success banner inside the modal stays up for 2 seconds before auto-closing — if you blinked you may have missed it. Check your Gmail / Outlook Sent folder; a copy of the outbound is there too.
"Got an error about email provider" The form sends through YOUR connected Gmail or Outlook. Reconnect in Settings → Email Integration, then resend. As a fallback you can email the support address directly — it's the same inbox.
"My .zip / .docx attachment didn't show up in the support email" Only JPG, PNG, GIF, WebP, PDF, TXT, and CSV files are accepted — other types are dropped at send time. Upload to Drive / Dropbox and paste the link in the description instead.
Open the Contact Support form

Notifications

Click the bell icon in the bottom-left sidebar (above the Settings gear) to open the notification panel. It slides in over the sidebar and shows up to 50 most recent notifications, newest first, with a red unread badge on the bell itself.

How fresh is the badge?

Notifications are in-app only — there's no browser permission prompt, no OS-level pop-ups:

  • Every 20 seconds the CRM checks for new notifications and updates the badge and panel list.
  • Every 60 seconds three local checks run: overdue tasks, upcoming meetings (within 15 min), and scheduled posts past their fire time.
  • No browser permission prompt — nothing to click "Allow" on.

Worst-case lag from event to badge is about a minute. Events that come from outside the CRM (payments, video plays, contract signs) land instantly on the next 20-second refresh.

Notification types fired today

  • New lead — fires when a fresh contact appears in your list (most commonly a web form submission). Click jumps to the contact.
  • Overdue task — one ping per task the first time it crosses its due date. Click jumps to the Tasks tab with the task highlighted.
  • Upcoming meeting — 15-minute heads-up before any calendar event starts. Click jumps to the Calendar tab.
  • Voicemail / Missed call — fires when a voicemail lands or an inbound call is missed. Click jumps to the Calls tab and opens that record.
  • Deal won / Payment received / Payment failed / Subscription started / Subscription canceled — fires automatically when your payment processor notifies us. Payment events also refresh your contact list so a lead that auto-converted to a customer shows up under Customers without you reloading.
  • Video watched — the first time a recipient plays a video you sent them. The notification opens the right contact card even if your CRM has reorganized since the send.
  • Time to post — scheduled LinkedIn / Facebook posts ping you the minute their scheduled time arrives.
  • Low stock — fires when product stock crosses its threshold after a Closed-Won deal decrements inventory.
  • Team join — fires to the org owner when an invited teammate accepts.
  • Storage limit — soft warning when your contact count crosses 2,500.

Click-to-navigate behavior

Every notification knows where to take you. Clicking switches to the right tab and opens the relevant record:

  • Contacts / Customers — opens the contact detail panel.
  • Tasks — scrolls to and highlights the specific task in the list.
  • Calls — opens the call detail panel (recording + transcript + notes).
  • Products — opens the product detail panel.
  • Calendar / Financial / Contracts / Scheduled Posts / Settings — switches to the tab and pops up a toast with the notification title so you know what triggered the click; the tab's own list is your jumping-off point.
Deleted records don't dead-end If the linked record was deleted or archived between the notification firing and your click, you get a "That item may have been deleted or archived" toast instead of a silent no-op — useful when you click an overdue-task ping for a task a teammate already completed and removed.

Mark all read + per-item read state

  • Click any individual notification to mark it read (and navigate).
  • The Mark all read button in the panel header flips every row in one click; the badge zeroes out immediately.
  • Unread items have a blue left border and bolder title text; read items go quiet.

Snooze persistence (tasks)

When you snooze an overdue-task notification for 1 hr / tomorrow / next week, the snooze sticks — it survives a browser reload and won't re-ping you on the next 60-second check. Earlier versions stored snooze locally only, so a refresh would resurrect every snoozed ping.

Common confusion points

"My badge says 3 but the panel only shows 2" The panel shows the 50 most recent notifications. If you have more than 50 unread items, the badge counts them all but only the latest 50 render. Hit Mark all read to reset and the discrepancy resolves.
"No browser notification popped up" Expected. Notifications are in-app only — no OS-level pop-ups. Keep the CRM tab open in the background; the badge updates whether or not the tab is foregrounded.
"I closed the tab and missed events" Notifications about outside events (payments, video plays, scheduled-post timing) DO still get recorded even with the tab closed — they show up next time you log in. The two local checks for overdue tasks and upcoming meetings only run while the tab is open, so those timers pause when the tab is closed.
Open the CRM

Storage Modes

CrawlSpace stores your contacts in one of two places: Spreadsheet mode (your own Google Sheet or OneDrive Excel file as the source of truth) or CrawlSpace mode (our cloud database). The same CRM features work in both; the difference is where the rows live and which features are available. Set it in Settings → Database Storage.

Spreadsheet mode — what it does

  • Reads from and writes to your own Google Sheet or OneDrive Excel file.
  • Activity logs append into columns AN+ of the same sheet, so the audit trail travels with the data.
  • Cancel CrawlSpace tomorrow and your data is still sitting in your Drive / OneDrive — no export step required.

CrawlSpace mode — what it does

  • Contacts live in our cloud database, shared across your whole org so every team member sees the same list.
  • Native CSV import, manual add, and the one-way Spreadsheet → CrawlSpace migration tool live in Settings → Database Storage.
  • Uses a fixed contact schema (no per-user column mappings), so the Column Mappings nav item is hidden in this mode.

Switching modes — what actually happens

Each backend's data stays in place Switching modes does NOT merge the two stores. If you set up a sheet, switch to CrawlSpace mode, add leads for a month, then switch back to Spreadsheet mode, you'll see your sheet contacts — not the CrawlSpace ones, which stay in the database untouched. New rows you create in one mode do not appear in the other.

To consolidate, use the Migrate Existing Spreadsheet button in Database Storage settings. It one-way copies all contacts from your sheet into CrawlSpace storage, with email + name+company dedup against the org's existing CrawlSpace rows so re-running is safe.

What gets migrated, what doesn't

  • Migrated: contact records (every field the schema covers), in batches of 25. Each migrated contact gets a stable identifier that persists across reloads.
  • Not migrated: deals, tasks, sequence enrollments, call history, scheduled emails, invoices. These stay associated with sheet mode and reappear if you switch back. If you've relied on them in sheet mode, plan to either recreate them in CrawlSpace mode or keep the sheet connection alive for read-only history.

Two-way sync (Spreadsheet mode only)

Sync runs in both directions but the cadence is asymmetric:

  • CRM → Sheet: instant on save. The moment you change a status, log a call, or edit a phone number in the CRM, the corresponding sheet cell updates.
  • Sheet → CRM: on demand. Click the circular-arrow Sync button in the sidebar, or wait for the auto-refresh tick (every 5 minutes if you enabled it in Settings). A row you edit in Sheets lands in the CRM on the next pull, not in real time.

Conflict resolution is last-write-wins. If you and a teammate edit the same row in both places between syncs, whichever write reaches the sheet last is what you'll see — usually the CRM write, since it's instant. To avoid the rare collision, pick one place to edit a given row for a given session.

Feature parity — not 100%

Most features work identically in both modes. A few only work in CrawlSpace mode (or in Spreadsheet mode with Google Sheets specifically):

  • Web forms / QR forms: Google Sheets sheet-mode and CrawlSpace mode both accept form submissions. Excel/OneDrive form receivers are coming soon — if you're in Excel sheet mode, form submissions return an error pointing you at Google Sheets or CrawlSpace mode.
  • Lead-to-customer auto-conversion on invoice paid: requires CrawlSpace mode. In Spreadsheet mode you set the Closed-Won status by hand.
  • Cross-source custom reports: joining contacts with marketing sends, invoice history, or revenue timeline needs CrawlSpace mode (spreadsheet rows can't be combined that way).
  • Webhook-fed lead intake: Facebook Lead Ads + LinkedIn Lead Gen drop straight into CrawlSpace storage; Spreadsheet mode silently misses these because the webhooks can't reach your sheet in real time.

Org-shared by default

Every CrawlSpace-mode contact is shared with your team CSV imports, manually-added contacts, and migrated contacts all land in your org's shared list, so every teammate sees them. If a write ever fails because your org membership is in an odd state, you'll get a "Could not resolve your organization" message instead of a silent half-import — refresh and try again, or reconnect from Settings.

Common confusion points

"I added a lead in CrawlSpace mode last week — now I switched to Sheet mode and it's gone" Expected. Each mode has its own separate list. The CrawlSpace lead is still safely stored; switch back to CrawlSpace mode and it reappears. There is no "merge both" toggle — if you want one unified list, run the migration in the direction you want consolidated.
"My Facebook Lead Ad webhook stopped creating contacts when I switched modes" Facebook and LinkedIn webhooks land in CrawlSpace storage; they can't reach your spreadsheet. If webhook intake is part of your funnel, stay in CrawlSpace mode.
"I edited a status in Google Sheets and the CRM still shows the old one" Sheet → CRM updates only land when the CRM pulls. Click the circular-arrow Sync button in the sidebar (or wait for the 5-minute auto-refresh if enabled). CRM → Sheets writes are immediate; the other direction is on demand.
"Migration says it ran but my deals didn't show up in CrawlSpace mode" The migration tool is contacts-only. Deals, tasks, sequence enrollments, call history, scheduled emails, and invoices are not copied across — they remain associated with the sheet-mode view and reappear if you switch back. Plan to recreate them in CrawlSpace mode or keep the sheet connection alive for read-only history.
Open Settings → Database Storage

Learning Library

The Learning Library is the in-app tutorial surface — not to be confused with this public Help Library, which is the marketing-site walkthrough you're reading now. Click the graduation-cap icon in the left sidebar after signing in to open the Learning Library: a categorized tile grid of step-by-step topics, some with interactive coachmark tours that highlight live UI elements while they explain.

What it covers

  • A growing topic library across categories like Getting Started, Sales & Deals, Communication, Automation, Settings & Customization, Team & Collaboration, Project Management, and Notifications.
  • Each topic has a written step list. About two-thirds also have an interactive coachmark tour ("Start Walkthrough") that runs inside the live app — spotlight, tooltip, optional action callbacks that switch tabs or open modals for you.
  • Topics without a coachmark tour hide the Start Walkthrough button entirely — you don't get a button that fires a "Walkthrough not available" toast.

Search and filter

The search input above the tile grid is a substring match against three fields per topic: title, one-line description, AND the full step body text. So a search like "Jaro Winkler" surfaces the Duplicate Detection topic even though that phrase only appears in the steps, and "voicemail drop" surfaces both the Calls topic and the Settings Voicemail Drops topic.

  • Input is debounced ~180 ms — typing a long query doesn't re-render on every keystroke.
  • Press Esc in the search input to clear the query.
  • Categories that have zero matching topics drop out of the rendered view so you don't see empty section headers.

Progress tracking

Each topic-detail modal has a Mark as Complete button. Completion is saved to your account so it follows you across devices.

The percent badge is drift-proof The overall completion percent counts only topics that currently exist in the library. If a topic gets renamed or removed across releases, your cached completion entry for the dead ID is ignored — so your percent doesn't silently jump up or down because of churn. Earlier versions counted the raw cache and the badge moved unexpectedly after topic re-organizations.

Walkthrough engine (coachmark tours)

The coachmark tour engine lives in tutorial-engine.js and is driven by a per-topic step list keyed by topic ID. Each step can include:

  • A selector for the live UI element to spotlight.
  • A title + text for the tooltip.
  • An optional action callback that switches tabs, opens modals, or temporarily replaces a panel's HTML with a mock so the user sees what the feature looks like without affecting their real data.
  • An optional waitFor selector so the tour pauses until the target element renders (useful when a step depends on a tab switch settling).

You can Prev / Next between steps, jump to any step via the tooltip header, or close the tour at any time — the engine cleans up the backdrop and any mock-HTML replacements on close.

Public help vs. in-app library

Two different surfaces, by design:

  • This Help Library (the page you're reading now on the marketing site) is publicly readable and covers the core feature set section-by-section. Good for evaluation before signup.
  • The in-app Learning Library (graduation-cap icon in the sidebar) is for signed-in users. It adds interactive walkthroughs that run against the live app, plus progress tracking and deeper how-tos you won't see in the public version.

Common confusion points

"The Start Walkthrough button is missing on this topic" Expected. Roughly a third of topics are written-only today — the button is hidden when no coachmark tour is defined, so the modal shows just the step list and the Mark as Complete button.
"My progress percent jumped without completing anything" Shouldn't happen anymore — the percent calculator filters completion entries against the live topic-ID set, so a release that renames or removes a topic doesn't move your number.
"Search isn't finding the topic I want" Try a shorter or different keyword — the search is a substring match, so "report builder" matches the Report Builder topic but "reports builder" doesn't. The step body is included in the match space, so try a phrase that probably appears in the walkthrough body.
Open the Learning Library

Settings

Settings is the org-wide configuration surface (distinct from Profile, which is per-user). The sidebar inside Settings groups related controls into sections; the visible set depends on your role (owner / admin / member) and your active permission profile. Members see only what their profile allows; owners and admins see everything.

Section map

  • Profile — editable form for the Profile tab: display name, business name, mailing address, timezone. Saved fields fan out to anywhere your name appears (sequences, signatures, contact-detail owner badges).
  • Database Storage — the Spreadsheet / CrawlSpace mode picker, sheet URL, column mappings, and the one-way migration tool. See the Storage Modes section for the full story on switching.
  • Column Mappings — spreadsheet mode only. Per-field column-letter assignments (A through AM). The Save action blocks any two fields pointing at the same letter and surfaces an error toast naming the conflict, so a misconfigured mapping doesn't silently overwrite one column with another.
  • Email Integration — connect Gmail or Outlook, edit your signature, set Send-As aliases, manage inbox sync. Signature image uploads cap at 2 MB and accept JPG / PNG / GIF / WebP only.
  • Phone & SMS — phone-number purchase, voicemail greeting, voicemail drops, recording settings, 10DLC + toll-free SMS registration. Voicemail audio uploads accept MP3 / M4A / WAV / WebM / OGG up to 3 MB.
  • Calendar & Booking — booking page slug, weekly hours, buffer rules, meeting-type list (15/30/45/60 min), round-robin assignment.
  • Organization & Team — member list, invites, seat counts, permission profile assignments. Profile changes apply immediately — the panel reloads after each save so two admins racing to change the same member's profile both see the same current state.
  • Permission Profiles — five preset profiles (Full Access, View Only, Sales, Project Manager, Marketing) plus custom profiles. A per-tab grid lets you set Edit / Use / View / Hide. Enforced in the UI AND at the database level, so a teammate can't work around the restriction.
  • Subscription & Billing — plan, payment method, invoices, cancel/downgrade flow. Owners only.
  • Tag Options — the controlled vocabulary for the Tag dropdown.
  • Integrations — Square, Stripe, Facebook Lead Ads, LinkedIn Lead Gen, Chrome Extension. Each tile shows connect / disconnect status.

Save semantics

"Settings saved" actually means saved The Save button writes locally first (instant), then saves to the cloud. If the cloud save fails (network, quota, etc.), you get a clear error toast naming the reason instead of a misleading "Settings saved successfully!" message. That earlier bug let users reload and find their edits silently reverted to the server's old values — fixed.

The Save button disables for the duration of the save (it relabels to "Saving...") so a rapid double-click can't fire two concurrent saves and leave the two writes in a half-old, half-new state.

Permission profile changes

When an admin changes a member's permission profile, the panel reloads immediately so both you and any other admin viewing the same panel see the change take effect. The member's own sidebar + tab visibility update on their next page load — we don't hot-swap mid-session. But the database enforces the restriction immediately regardless, so even if they kept the page open, any restricted action they tried would be blocked.

Multi-tab edits

Settings is last-write-wins across tabs. If you open Settings in two tabs and edit different fields in each, the second save overwrites the first (every save sends the full settings object). Reload Settings before editing in a second tab to start from current state.

Common confusion points

"Save said success but my change didn't stick after reload" Either (a) the cloud save actually failed and you missed the red error toast, or (b) another tab saved over your change. The error wording was tightened so a failed cloud save now reads "Saved locally, but cloud sync failed: <reason>" instead of a misleading green success.
"My column letter doesn't take effect" Two CRM fields probably point at the same letter — the Save action blocks this and surfaces a toast naming the conflict. Open the mapping table, change one of the two so they're unique, hit Save again.
"Member can still see a tab I just hid" Permission changes apply on their next page load. Any restricted action they try in the meantime is already blocked by the server. To force-apply immediately, ask them to refresh or sign out.
Open Settings

Mobile App

The CrawlSpace mobile app is a native Android app — not a phone-shaped view of the website. It has its own dialer, its own inbox-style SMS, its own task and calendar screens, and biometric sign-in. Built for field reps who spend half their day between meetings and need to actually transact from their phone instead of just looking at data. iOS is under evaluation; email support if iOS is critical for your team.

Signing in

  • Same email and password as the desktop CRM. No separate registration.
  • After your first sign-in, enable biometric login (fingerprint or face scan) for instant access on future opens.
  • Mobile and desktop sessions are independent — both can be active at the same time without conflict, and signing out on one doesn't sign you out on the other.

What's on the app

  • Dashboard — daily activity stats: calls made, emails sent, tasks completed, deals won, current pipeline.
  • Contacts — leads + customers with instant search. Tap any contact to see full activity history, edit fields, or kick off a call / text / email.
  • Communications — the full Twilio dialer (mute, hold, keypad, speaker, hang-up), two-way SMS in a chat-style view, voicemail inbox, and email compose with your signature.
  • Calendar + Tasks — today / overdue / this week task views, plus calendar event creation that syncs to Google Calendar or Outlook.
  • Reports + Settings — read-only access to your saved reports; settings inherited from the desktop CRM so there's nothing duplicated to configure.

Calling from the app

Outbound calls go through your connected Twilio business number, so the recipient sees your company — not your personal cell. After each call a disposition modal pops up so you can tag the outcome (Connected, Left Voicemail, No Answer, etc.) and add notes; the call lands in the same activity log the desktop sees.

Picture-in-Picture calls When you leave the app mid-call (press home, open another app, look something up), the call keeps going in a floating window. Tap the window to return. Perfect for "let me check that for you" moments without dropping the line.
Inbound calls don't double-ring When a customer calls your business number, the app rings with caller info. If you pick up on the desktop CRM first, the app stops ringing automatically — no double-rings across devices, no awkward "which device do I answer on" moment.

How to install

  • Direct download — from the desktop CRM, the Mobile App link in the marketing site footer (or the in-app help) points to the latest Android build for direct install.
  • Google Play Store — search "CrawlSpace CRM" and install normally. The Play version gets tested releases on a regular cadence; direct downloads get new features first.

Limitations

  • Android only today. iOS is being evaluated.
  • Needs a network connection — the app doesn't currently work offline. In a dead zone you can browse cached contacts but you can't make calls, send SMS, or sync tasks.
  • Settings live on the desktop — email connection, Twilio setup, sequence configuration, and other admin actions happen on desktop. Mobile reads those settings but doesn't duplicate the configuration UI.

Common confusion points

"It signed me out of my desktop session" It shouldn't — mobile and desktop sessions are independent. If desktop got signed out too, you clicked Sign Out on one of them (which explicitly signs out everywhere), not just closed the tab.
"I tapped a phone number and my phone's native dialer opened, not the CRM dialer" Tap the phone icon next to the number, not the raw number itself — the number is a generic link that hands off to your OS dialer. The phone icon is the in-app dialer.
"Biometric login isn't offered" Your device needs a fingerprint or face-scan enrolled in Android settings first. Once enrolled, sign in once with email + password and the app will offer to enable biometric for future opens.
See mobile app features

Chrome Extension

The CrawlSpace Chrome extension turns any web page into a one-click lead capture. Install it once, sign in once, then capture LinkedIn profiles, company About pages, conference attendee lists, podcast guest bios — any page with a name and contact info — into your CRM in seconds. Built for SDRs and anyone doing prospecting outside the CRM tab.

What it does

  • One-click LinkedIn profile capture — browse LinkedIn, find a promising profile, click the extension icon. The popup extracts name, title, current company, location, LinkedIn URL, and any publicly listed email. Review or edit in the popup, then create the lead in your CRM with one more click.
  • Works on any website — not limited to LinkedIn. The generic parser handles arbitrary pages; LinkedIn and company pages have tuned parsers for better field detection.
  • Auto-enrich existing contacts — if the email on the page matches a contact you already have, the popup switches to enrich mode and shows you what would change. One click updates the existing contact instead of creating a duplicate.
  • Theme-matched popup — the popup picks up your CRM's color theme so it doesn't feel like a separate tool.

Signing in

Install from the Chrome Web Store (or load it directly during early access). Open the popup once and sign in with your CrawlSpace email and password. The extension stays signed in across browser restarts — no per-rep config or activation codes.

What it can read on a page

  • LinkedIn profiles — first / last name, current title, current company, location, LinkedIn URL, profile photo, and email if publicly listed (rare on LinkedIn).
  • LinkedIn company pages — company name, industry, employee count, website, headquarters location.
  • Generic web pages — visible name, email, and company when detectable. The popup always lets you correct fields before saving, so a missed parse never becomes a bad lead.

How leads land

Captures write straight into your CRM's contact list — same database as your deals, calls, sequences, and reports. A lead you capture from LinkedIn is immediately eligible for drip sequences, newsletters, and dashboard reports. No separate sync, no lag.

Common use cases

  • LinkedIn prospecting runs — open a LinkedIn search, click through promising profiles, capture 60-80 leads per 2-hour block instead of 15-20 with manual retyping.
  • Event / conference attendee capture — scrape attendee lists from event websites and bulk-load them into the CRM the same day.
  • Refreshing stale contacts — find a contact on LinkedIn, see their title changed, click the extension, email-match triggers enrich mode, update in one click without duplication.

Limitations

  • Email isn't always on the page — LinkedIn profiles rarely show personal emails. The extension captures what's visible; type the email in manually on the confirmation panel if you have it from another source.
  • Generic-page parsing is best-effort — LinkedIn and major company pages have tuned parsers; other sites get a generic name/email/company sweep that may miss fields. The confirmation panel always lets you edit before saving.
  • Requires an active CrawlSpace subscription — the extension is a companion tool; the data goes into your CRM. No CRM, nothing to capture into.

Common confusion points

"I clicked the extension on a LinkedIn profile but no name was captured" LinkedIn re-renders profile pages as you scroll and switch views. Refresh the LinkedIn page so the full profile is loaded, then click the extension. If it still misses, type the fields manually in the popup — the save will go through.
"It created a duplicate even though I already have this person" The dedup check uses email. If LinkedIn didn't show their email and your existing contact's email isn't on the page, the extension can't match them. Either capture with the email field filled in, or merge the duplicate later from the CRM's Duplicate Resolver.
"I clicked Save but nothing appeared in the CRM" Open the CRM in another tab and refresh the contact list — the lead should be there. If not, the extension shows save errors in the popup; double-check you're signed in (the popup header shows your email when you are).
Get the extension

Still stuck?

Reach out and we'll walk through it with you on a call.

Contact support