What it does
- Projects (Dashboard → Projects) — one record per client job: title, internal description, client (name/email/phone, optionally linked to a Client Billing client and/or CRM contact), status (
in_progress/on_hold/completed/canceled), start/due dates. - Milestones — a checklist per project. Progress is
done / totalwhen milestones exist; otherwise a manual percent field. A completed project always reads 100%. - Progress updates — timestamped notes. "Visible to client" updates render on the tracking page; internal ones stay dashboard-only.
- Client tracking page — every project gets a private tokenized URL (
/project/{40-char-token}, no login) rendered in the site's full public branding: status, progress bar, milestone checklist, public update feed, and — once completed — a thank-you card with a review link (when Reviews is on). - Dashboard card — open-project count with an overdue subline (
projects_openwidget).
Starting a project from paperwork
An accepted proposal or a signed contract can open a project directly — the third leg of the agency pipeline, beside the proposal → draft-invoice conversion that already existed. The row menu on each list gains Start a project, which carries over the client (name, email, phone, the Client Billing link and the CRM contact), uses the document title, and turns its scope/body into the project's description so the tracking page opens with what was actually agreed.
It's manual by design: the operator decides when paperwork becomes live work, and a signed contract routinely precedes any work starting. The project records what it came from in a source_type/source_id morph, which makes the action idempotent — clicking twice returns the same project, and the menu item disappears once one exists.
Both lists route through the one ProjectStarter; neither module reimplements project creation.
Completion automations
Marking a project complete (the Mark Complete button, which shows exactly what will fire before you confirm) runs:
- CRM (when enabled): the client is upserted as a contact (
source: project), the contact id is backfilled onto the project, and "Project completed: {title}" is logged on their timeline. - Marketing sequence enrollment (when enabled): the client is enrolled in the project's completion sequence — per-project override, falling back to the global default (Projects → Settings). Enrollment works through the CRM contact when CRM is on, or a marketing subscriber row otherwise. Sequences enroll each person once, ever, so re-completing can't restart a drip.
- Automatic review request (Reviews feature): the
projecttrigger in Reviews → Settings creates a pending review request thatreviews:send-requestsdelivers after the configured delay, with the usual per-email cooldown and once-per-(origin, origin_id) dedupe. Leave this OFF if your completion sequence already asks for a review — otherwise the client is asked twice. - Webhook:
project.completedfires with id/title/client/progress/tracking URL/completed_at.
Reopen (status back to in-progress) and re-complete safely: the review request dedupes on (origin: project, origin_id) and sequence enrollment is once-per-person.
Review follow-up sequences (lives in Marketing, works without Project Tracking)
The follow-up flow deliberately lives in the Marketing feature so it can be started for any job — tracked here or not:
- "Review follow-up" template — Marketing → Sequences → New Sequence → Start from: Template: Review follow-up. Creates a draft 3-step manual sequence: day-1 "does everything look good?" check-in, day-4 thank-you with a Leave a review button, day-8 nudge that only sends when the recipient hasn't left a review yet.
{{review_link}}token — usable in any campaign or sequence email (button URL or body) and SMS. At send time each recipient gets their own trackable review-funnel link (aReviewRequestwithorigin: marketing, created as already-sentso the reviews sender never double-delivers it; an existing unanswered request from the last 60 days is reused so a nudge carries the same token). Click tracking never rewrites the token's href, so funnelopened_at/completed_atstay the source of truth. Requires Reviews; with Reviews off the token degrades to the site home URL.- Review step conditions —
Only if they left a review or gave feedback/Only if they haven't left a review yet(shown when Reviews is enabled). Evaluated when the step comes due against the recipient's review requests since enrollment: rated (private feedback / first-party review) or clicked through to a platform both count as "responded". - Manual starts — enroll by email on the sequence edit page, from the Enroll in Sequence button on a CRM contact, or via a CRM automation rule (
enroll_sequenceaction).
Configuration
- Dashboard → Projects → Settings (admin): default completion sequence for new projects (each project can override with a specific sequence or "Don't enroll").
- Dashboard → Reviews → Settings: the "After a project is completed" auto-request toggle + shared delay/cooldown.
- Feature toggle: Settings → Features → Clients & Billing → Project Tracking (
project_tracking, opt-in).
Mechanics / files
- Module:
app/Features/ProjectTracking/— modelsProject(client_projects, distinct from theprojectsportfolio content type),ProjectMilestone,ProjectUpdate; supportProjectCompletion(completion side-effects, all fire-and-forget),ProjectStarter(proposal/contract → project) andProjectSettings. - Public route
project/{token}(['web', 'feature:project_tracking'], transactional — never response-cached); dashboard routes underdashboard/projects(role:manager, settingsrole:admin). - Member portal route
members/projects(+ auth) →project_tracking::public.my-projects, plus the Projects pill inpartials/account-navand a card on the account dashboard. It lists the signed-in account's projects and links each one to the tokenized tracker above — the tracker stays the only place a project's detail renders. See memberships.md → The account area. - Cross-feature listeners live in the consuming modules (no coupling in this one): the review trigger in
ReviewRequestTriggers::register()(Reviews), the webhook inWebhooksServiceProvider::registerEventHooks(). - Marketing pieces:
SequenceTemplates(template factory),ReviewLinks(Reviews-side surface minting/reading funnel links for other features), review conditions inSequenceRunner::conditionMet(),{{review_link}}substitution inSequenceRunner+SendCampaignJob+EmailRenderer::personalize(). - Tests:
ProjectTrackingTest,ProjectTrackingCompletionTest,MarketingReviewLinkTest,PipelineLinksTest(starting from paperwork).
Limitations
- Matching a project to a signed-in account is by email (the project's
client_email, or the linked invoicing client's) — a client who books under one address and signs in with another sees an empty list. - One flat milestone list (no phases/nesting, no per-milestone due dates).
- Completion email content comes from the sequence you pick; there is no separate hardcoded "check-in" email.