Tasks — Technical Reference
This page covers the data models, completion lifecycle, and daily digest architecture behind the Tasks feature.
Data Models
tasks
| Column | Type | Description |
|---|---|---|
id | uuid | Primary key |
workspace_id | uuid | FK → workspaces.id — tenant isolation (ON DELETE CASCADE) |
title | text | Required. Short description of the action item |
description | text | Optional detailed notes (defaults to empty string) |
due_date | timestamptz | When the task is due. Drives the dashboard widget and daily digest |
status | text | 'open' or 'completed' (CHECK constraint) |
contact_id | uuid | FK → contacts.id (ON DELETE SET NULL) — optional contact link |
deal_id | uuid | FK → deals.id (ON DELETE SET NULL) — optional deal link |
assigned_to | uuid | FK → user_profiles.id — assigned workspace member |
completed_at | timestamptz | Timestamp when marked as completed (NULL if open) |
reminder_sent_at | timestamptz | Used by the daily digest to prevent duplicate notifications |
created_by | uuid | FK → user_profiles.id — who created the task |
created_at | timestamptz | When the task was created |
updated_at | timestamptz | When the task was last modified |
stripe_payment_intent_id | text | Stripe Payment Intent ID (e.g. pi_xxx) associated with automated refund review tasks |
Soft links: Contact and deal FKs use
ON DELETE SET NULL— if the linked contact or deal is deleted, the task survives as a general to-do item.
Architecture
Completion Lifecycle
| Action | Field Changes |
|---|---|
| Complete | status → 'completed', completed_at → current timestamp |
| Reopen | status → 'open', completed_at → NULL, reminder_sent_at → NULL |
Clearing reminder_sent_at on reopen ensures the task re-enters the daily digest cycle.
Task List Sorting & getTasks
The getTasks server action retrieves a paginated, filtered, and sorted list of tasks.
export interface TaskListParams {
page?: number;
pageSize?: number;
search?: string;
sortBy?: string;
sortDir?: "asc" | "desc";
}Sorting & Parameters:
sortBy: Specifies the column to sort by. Supported values:"due_date"(default): Sorts chronologically bydue_dateusing a date-aware stacked-bar sort icon.
sortDir: The sort direction ("asc"or"desc"). If undefined, defaults to"asc".- Null Handling: Tasks without a due date (where
due_dateisNULL) are always forced to the bottom of the list (nulls last), regardless of whether sorting is ascending or descending. - Status Independence: The sorting settings apply uniformly across all status tabs (Open, Completed, or Any), decoupling task sorting from status filter selections.
Consolidated Dialog Components
To eliminate UI redundancy and synchronize task management across multiple dashboards, task creation and detailed viewing/editing are consolidated into two self-hydrating React components:
TaskCreateDialog: A unified dialog for creating new tasks. It supports context-aware contact and deal prefilling, handles the cascading picker logic, and dynamically toggles the visibility of the deal selector based on the workspace'sdealsEnabledsetting.TaskDetailDialog: A centralized interface for task detail visualization, field modification, task deletion, and note CRUD management (with workspace note templates support). To prevent linked records from disappearing during completion toggles, the dialog uses a functional state update pattern to merge raw database updates fromtoggleTaskStatuswhile preserving the nested contact and deal relationship objects.
These components are used across 5 main CRM surfaces: Dashboard, Tasks page, Notes page, Contacts detail page, and Deals board.
Cascading Contact / Deal Pickers
Inside TaskCreateDialog and TaskDetailDialog, the contact and deal pickers work in tandem to ensure clean relational data binding:
- Selecting a deal → auto-fills
contact_idwith the deal's linked contact. - Selecting a contact → filters the deal dropdown list to show only the selected contact's deals.
- Changing the contact → clears any existing, now incompatible
deal_idselection.
When creating a task from a Contact Detail card, the contact is preselected and locked. When creating from a Deal Detail page, both the deal and contact fields are prefilled.
Deep Linking
Tasks support URL-based deep linking: /tasks?taskId=<id> automatically opens the target task's TaskDetailDialog. This is used by Global Search and the Notification Center to display specific tasks instantly.
When a deep link is resolved, the parent Tasks page automatically widens its view filters (setting the status filter to "Any" and the assignee filter to "Everyone's") to guarantee that the target task is visible and loaded in the list regardless of its assignee or completion status.
Completed Today Section
The "Completed Today" section displays tasks completed within the current day. Tasks are rendered with strikethrough text and reduced opacity. The section respects the active assignee filter — filtering to "Mine" shows only your completed tasks.
Present on four surfaces: /tasks page, dashboard widget, Contact Detail, Deal Detail.
Daily Digest Architecture
Cron Configuration
| Property | Value |
|---|---|
| Schedule | Daily at 8:00 AM US Central (1:00 PM UTC) |
| Cron expression | 0 13 * * * |
| Endpoint | /api/cron/task-digest |
| Authentication | Authorization: Bearer {CRON_SECRET} |
| Email type | Transactional (system notification) |
| Sender | PLATFORM_SENDER_EMAIL |
{
"path": "/api/cron/task-digest",
"schedule": "0 13 * * *"
}Digest Pipeline
- Authenticate — Verify
CRON_SECRETbearer token - Fetch eligible tasks — Query:
status = 'open',assigned_to IS NOT NULL,due_date ≤ end of today,reminder_sent_atis NULL or before today - Filter already-reminded — Exclude tasks where
reminder_sent_atmatches today - Group by user — Each user receives one email regardless of task count
- Build digest — Resolve user email via
auth.admin.getUserById(), classify tasks into "Due Today" and "Overdue", build HTML email - Send and stamp — Send via Resend, stamp
reminder_sent_aton all included tasks
Deduplication Logic
The reminder_sent_at field prevents duplicate emails within the same day. Overdue tasks
re-appear in the next day's digest because their reminder_sent_at will be yesterday's
date. Reopened tasks have reminder_sent_at cleared, so they immediately re-enter the
digest cycle.
Email Template
| Section | Style |
|---|---|
| Header | Green gradient (#059669 → #10b981), "Gordon CRM — Task Digest" |
| Greeting | Personalized: "Good morning, {first_name}!" |
| Overdue | Red heading (⚠️), red border, task title + contact/deal + original due date |
| Due Today | Standard heading (📋), task title + contact/deal |
| CTA | Green button → /tasks |
| Footer | "This is an automated notification from Gordon CRM." |
Workspace Grouping
For users who are members of multiple workspaces, both the task digest and the appointment digest templates support automatic workspace grouping.
- Trigger: If the digest contains tasks/appointments from more than one workspace, a bold workspace name subheader is displayed above the items belonging to that workspace.
- Single Workspace: If all tasks/appointments belong to a single workspace, no workspace subheaders are rendered, keeping the email layout clean.
Related Cron Jobs
| Cron Job | Schedule | Purpose |
|---|---|---|
| Campaign Sweeper | Every 5 minutes | Process due campaign enrollments |
| Broadcast Sweeper | Every 5 minutes | Process scheduled broadcasts |
| Birthday Sweeper | Daily at 8:00 AM UTC | Fire birthday automation triggers |
| Task Digest | Daily at 1:00 PM UTC | Send daily task summary emails |
| Mention Digest | Daily at 2:00 PM UTC | Send batched unread mention emails |
Automation Integration
The create_task automation action creates a task pre-linked to the triggering contact
with configurable fields:
title— Task title (supports template variables)description— Optional descriptionassigned_to— Workspace member to assigndue_date_offset— Number of days from trigger to set the due date
See Automations → Actions for the full specification.
Performance & Hydration Optimization
Server/Client Page Split & Lazy Loading
- Server Component (
pages/tasks/page.tsx): Resolves workspace context and pre-fetches the initial task directory bundle on the server, avoiding rendering waterfalls. - Client Component (
tasks-client.tsx): Manages client-side lists rendering, task search filters, sorting, and pagination. - Dynamic Imports: Defer loading of heavy components (
TaskCreateDialogandTaskDetailDialog) usingnext/dynamicdynamic imports withssr: falseto only download chunks on user interaction. - Hydration Guards: Integrates a workspace transition synchronization gate via
initialWorkspaceIdRefand a client mount ref guard (isFirstQueryRef) to prevent duplicate mounting API fetches.
Security
RLS Policies
| Operation | Policy |
|---|---|
| SELECT | All workspace members can view tasks |
| INSERT | All workspace members can create tasks |
| UPDATE | All workspace members can edit tasks |
| DELETE | Admin or Owner only |