Headlessly
CRM

Activity

Calls, emails, meetings, demos, and follow-ups -- every interaction logged against contacts and deals.

Schema

import { Noun } from 'digital-objects'

export const Activity = Noun('Activity', {
  subject: 'string!',
  type: 'Call | Email | Meeting | Task | Note | Demo | FollowUp',
  description: 'string',
  deal: '-> Deal.activities',
  contact: '-> Contact.activities',
  organization: '-> Organization',
  campaign: '-> Campaign',
  assignee: '-> Contact',
  createdBy: '-> Contact',
  dueAt: 'datetime',
  startAt: 'datetime',
  endAt: 'datetime',
  duration: 'number',
  allDay: 'boolean',
  timezone: 'string',
  status: 'Pending | InProgress | Completed | Cancelled',
  priority: 'Low | Medium | High | Urgent',
  completedAt: 'datetime',
  outcome: 'string',
  recordingUrl: 'string',
  meetingLink: 'string',
  reminderAt: 'datetime',
  complete: 'Completed',
  cancel: 'Cancelled',
  log: 'Logged',
})

Fields

FieldTypeRequiredDescription
subjectstringYesActivity subject line or title
typeenumNoCall, Email, Meeting, Task, Note, Demo, or FollowUp
descriptionstringNoDetailed notes or body content
deal-> DealNoDeal this activity relates to
contact-> ContactNoContact this activity involves
organization-> OrganizationNoOrganization this activity relates to
campaign-> CampaignNoCampaign this activity is part of
assignee-> ContactNoPerson responsible for this activity
createdBy-> ContactNoPerson who created this activity
dueAtdatetimeNoDeadline for tasks and follow-ups
startAtdatetimeNoStart time for meetings and calls
endAtdatetimeNoEnd time for meetings and calls
durationnumberNoDuration in minutes
allDaybooleanNoWhether this is an all-day event
timezonestringNoIANA timezone for the activity
statusenumNoPending, InProgress, Completed, or Cancelled
priorityenumNoLow, Medium, High, or Urgent
completedAtdatetimeNoTimestamp when the activity was completed
outcomestringNoResult or notes from the completed activity
recordingUrlstringNoURL to a call or meeting recording
meetingLinkstringNoVideo meeting URL
reminderAtdatetimeNoTimestamp for a reminder notification

Relationships

FieldDirectionTargetDescription
deal->Deal.activitiesThe deal this activity is part of
contact->Contact.activitiesThe contact this activity involves
organization->OrganizationThe organization this activity relates to
campaign->CampaignMarketing campaign this activity belongs to
assignee->ContactPerson assigned to complete this activity
createdBy->ContactPerson who logged this activity

Verbs

VerbEventDescription
createCreatedCreate a new activity
updateUpdatedUpdate activity fields
deleteDeletedDelete an activity
completeCompletedMark the activity as done with an outcome
cancelCancelledCancel a pending or in-progress activity
logLoggedLog a past activity (call notes, meeting summary)

Verb Lifecycle

import { Activity } from '@headlessly/crm'

// BEFORE hook -- validate completion
Activity.completing(activity => {
  if (!activity.outcome) throw new Error('Outcome required when completing an activity')
})

// Execute -- complete the activity
await Activity.complete('activity_e5JhLzXc')

// AFTER hook -- schedule follow-up
Activity.completed((activity, $) => {
  if (activity.type === 'Demo') {
    $.Activity.create({
      subject: `Follow up after demo: ${activity.subject}`,
      type: 'FollowUp',
      contact: activity.contact,
      deal: activity.deal,
      assignee: activity.assignee,
      dueAt: new Date(Date.now() + 2 * 86_400_000).toISOString(),
      status: 'Pending',
      priority: 'High',
    })
  }
})

Status State Machine

Pending --> InProgress --> Completed
   \            \
    \            \
     -----------> Cancelled
  • Pending: Scheduled or created, not yet started
  • InProgress: Currently underway (e.g., an ongoing meeting)
  • Completed: Finished with an outcome recorded
  • Cancelled: Cancelled before completion

Activity Types

TypeUse CaseTypical Fields
CallPhone calls, voice chatsduration, recordingUrl, outcome
EmailEmail correspondencedescription (email body), outcome
MeetingScheduled meetingsstartAt, endAt, meetingLink, duration
TaskAction items and to-dosdueAt, priority, assignee
NoteInternal notes and observationsdescription
DemoProduct demonstrationsstartAt, endAt, meetingLink, outcome
FollowUpScheduled follow-up actionsdueAt, priority, assignee

Cross-Domain Patterns

Activity is the interaction log that gives context to every other entity:

  • CRM (Deal): Activities track every touchpoint on a deal. Deal.activities gives a timeline of the entire sales process. Deals without recent activities may trigger rotting alerts.
  • CRM (Contact): Contact.activities shows the full interaction history with a person. The lastEngagement field on Contact is derived from the most recent activity.
  • Marketing: Campaign-linked activities track outreach and engagement. Demo requests from campaigns feed back into attribution.
  • Support: Support interactions can be logged as activities to maintain a unified timeline.
  • Analytics: Activity events (created, completed, cancelled) feed engagement metrics. Activity counts per deal stage reveal bottlenecks.
import { Activity } from '@headlessly/crm'

// Update the contact's last engagement when an activity is logged
Activity.logged((activity, $) => {
  if (activity.contact) {
    $.Contact.update(activity.contact, {
      lastEngagement: new Date().toISOString(),
    })
  }
})

Query Examples

SDK

import { Activity } from '@headlessly/crm'

// Find overdue tasks
const overdue = await Activity.find({
  type: 'Task',
  status: 'Pending',
  dueAt: { $lt: new Date().toISOString() },
})

// Get activities for a specific deal
const dealActivities = await Activity.find({
  deal: 'deal_k7TmPvQx',
  status: 'Completed',
})

// Log a completed call
await Activity.log({
  subject: 'Discovery call with Alice',
  type: 'Call',
  contact: 'contact_fX9bL5nRd',
  deal: 'deal_k7TmPvQx',
  duration: 30,
  outcome: 'Interested in enterprise plan, scheduling demo next week',
  status: 'Completed',
})

MCP

headless.ly/mcp#search
{
  "type": "Activity",
  "filter": { "deal": "deal_k7TmPvQx", "type": "Meeting" },
  "sort": { "startAt": "desc" },
  "limit": 10
}

REST

# List activities for a deal
curl https://crm.headless.ly/~acme/activities?deal=deal_k7TmPvQx

# Get a specific activity
curl https://crm.headless.ly/~acme/activities/activity_e5JhLzXc

# Create a meeting activity
curl -X POST https://crm.headless.ly/~acme/activities \
  -H 'Content-Type: application/json' \
  -d '{"subject": "Demo with Acme", "type": "Demo", "contact": "contact_fX9bL5nRd", "deal": "deal_k7TmPvQx", "startAt": "2025-03-15T14:00:00Z", "duration": 45}'

# Complete an activity
curl -X POST https://crm.headless.ly/~acme/activities/activity_e5JhLzXc/complete

On this page