Agent Skills: HubSpot Core Workflow B: Marketing & Tickets

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/hubspot-core-workflow-b

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-b

Skill Files

Browse the full folder contents for hubspot-core-workflow-b.

Download Skill

Loading file tree…

plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-b/SKILL.md

Skill Metadata

Name
hubspot-core-workflow-b
Description
|

HubSpot Core Workflow B: Marketing & Tickets

Overview

Marketing automation workflow: manage contact lists, process form submissions, send marketing emails, and create support tickets. Complements the sales pipeline in Workflow A.

Prerequisites

  • Completed hubspot-install-auth setup
  • Scopes: crm.lists.read, crm.lists.write, content, forms, crm.objects.marketing.emails.read
  • Marketing Hub subscription (Starter+ for emails)

Instructions

Step 1: Create and Manage Contact Lists

import * as hubspot from '@hubspot/api-client';

const client = new hubspot.Client({
  accessToken: process.env.HUBSPOT_ACCESS_TOKEN!,
  numberOfApiCallRetries: 3,
});

// Create a static contact list
// POST /crm/v3/lists/
async function createStaticList(name: string): Promise<string> {
  const response = await client.apiRequest({
    method: 'POST',
    path: '/crm/v3/lists/',
    body: {
      name,
      objectTypeId: '0-1', // contacts
      processingType: 'MANUAL', // static list
    },
  });
  const data = await response.json();
  return data.listId;
}

// Add contacts to a static list
// PUT /crm/v3/lists/{listId}/memberships/add
async function addToList(listId: string, contactIds: string[]): Promise<void> {
  await client.apiRequest({
    method: 'PUT',
    path: `/crm/v3/lists/${listId}/memberships/add`,
    body: contactIds.map(Number),
  });
}

// Create a dynamic list with filter criteria
async function createDynamicList(name: string): Promise<string> {
  const response = await client.apiRequest({
    method: 'POST',
    path: '/crm/v3/lists/',
    body: {
      name,
      objectTypeId: '0-1',
      processingType: 'DYNAMIC',
      filterBranch: {
        filterBranchType: 'AND',
        filters: [
          {
            filterType: 'PROPERTY',
            property: 'lifecyclestage',
            operation: {
              operationType: 'MULTISTRING',
              operator: 'IS_ANY_OF',
              values: ['lead', 'marketingqualifiedlead'],
            },
          },
        ],
      },
    },
  });
  const data = await response.json();
  return data.listId;
}

Step 2: Process Form Submissions

// Handle a HubSpot form submission via the Forms API
// POST /submissions/v3/integration/secure/submit/{portalId}/{formGuid}
async function submitForm(
  portalId: string,
  formGuid: string,
  fields: Record<string, string>,
  context: { pageUri?: string; ipAddress?: string }
): Promise<void> {
  await client.apiRequest({
    method: 'POST',
    path: `/submissions/v3/integration/secure/submit/${portalId}/${formGuid}`,
    body: {
      submittedAt: Date.now(),
      fields: Object.entries(fields).map(([name, value]) => ({
        objectTypeId: '0-1',
        name,
        value,
      })),
      context: {
        pageUri: context.pageUri || '',
        ipAddress: context.ipAddress || '',
      },
    },
  });
}

// Retrieve form submissions
// GET /form-integrations/v1/submissions/forms/{formGuid}
async function getFormSubmissions(formGuid: string, limit = 50) {
  const response = await client.apiRequest({
    method: 'GET',
    path: `/form-integrations/v1/submissions/forms/${formGuid}?limit=${limit}`,
  });
  return response.json();
}

Step 3: Create Support Tickets

// POST /crm/v3/objects/tickets
async function createTicket(
  contactId: string,
  subject: string,
  description: string,
  priority: 'LOW' | 'MEDIUM' | 'HIGH'
): Promise<string> {
  // Get support pipeline
  const pipelines = await client.crm.pipelines.pipelinesApi.getAll('tickets');
  const supportPipeline = pipelines.results[0];
  const newStage = supportPipeline.stages.find(s => s.label === 'New')
    || supportPipeline.stages[0];

  const ticket = await client.crm.tickets.basicApi.create({
    properties: {
      subject,
      content: description,
      hs_pipeline: supportPipeline.id,
      hs_pipeline_stage: newStage.id,
      hs_ticket_priority: priority,
      source_type: 'API',
    },
    associations: [
      {
        to: { id: contactId },
        types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 16 }],
      },
    ],
  });

  console.log(`Created ticket ${ticket.id}: ${subject}`);
  return ticket.id;
}

// Update ticket stage
async function closeTicket(ticketId: string): Promise<void> {
  const ticket = await client.crm.tickets.basicApi.getById(
    ticketId, ['hs_pipeline']
  );
  const pipelines = await client.crm.pipelines.pipelinesApi.getAll('tickets');
  const pipeline = pipelines.results.find(p => p.id === ticket.properties.hs_pipeline);
  const closedStage = pipeline?.stages.find(
    s => s.label === 'Closed' || s.label === 'Done'
  );

  if (closedStage) {
    await client.crm.tickets.basicApi.update(ticketId, {
      properties: { hs_pipeline_stage: closedStage.id },
    });
  }
}

Step 4: Create Tasks for Follow-up

// POST /crm/v3/objects/tasks
async function createFollowUpTask(
  contactId: string,
  subject: string,
  dueDate: Date,
  ownerId: string
): Promise<string> {
  const task = await client.crm.objects.tasks.basicApi.create({
    properties: {
      hs_task_subject: subject,
      hs_task_body: `Follow up with contact ${contactId}`,
      hs_task_status: 'NOT_STARTED',
      hs_task_priority: 'MEDIUM',
      hs_timestamp: dueDate.toISOString(),
      hubspot_owner_id: ownerId,
    },
    associations: [
      {
        to: { id: contactId },
        types: [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 204 }],
      },
    ],
  });

  return task.id;
}

Step 5: Search Across CRM Objects

// POST /crm/v3/objects/{objectType}/search
async function searchCRM(
  objectType: 'contacts' | 'companies' | 'deals' | 'tickets',
  query: string,
  properties: string[]
) {
  const searchRequest = {
    filterGroups: [{
      filters: [{
        propertyName: objectType === 'contacts' ? 'email' : 'name',
        operator: 'CONTAINS_TOKEN' as const,
        value: `*${query}*`,
      }],
    }],
    properties,
    limit: 20,
    after: 0,
    sorts: [{ propertyName: 'createdate', direction: 'DESCENDING' as const }],
  };

  switch (objectType) {
    case 'contacts':
      return client.crm.contacts.searchApi.doSearch(searchRequest);
    case 'companies':
      return client.crm.companies.searchApi.doSearch(searchRequest);
    case 'deals':
      return client.crm.deals.searchApi.doSearch(searchRequest);
    case 'tickets':
      return client.crm.tickets.searchApi.doSearch(searchRequest);
  }
}

Output

  • Static and dynamic contact lists created
  • Form submissions processed and retrieved
  • Support tickets with pipeline stages
  • Follow-up tasks created and assigned
  • Cross-object CRM search

Error Handling

| Error | Code | Cause | Solution | |-------|------|-------|----------| | LIST_NOT_FOUND | 404 | Invalid list ID | Verify list exists in HubSpot | | FORM_NOT_FOUND | 404 | Invalid form GUID | Check form ID in Marketing > Forms | | INVALID_PIPELINE_STAGE | 400 | Stage not in pipeline | Fetch pipeline stages first | | SCOPE_MISSING | 403 | Missing forms or content scope | Add scope to private app |

Examples

Complete Marketing Workflow

async function onNewSignup(email: string, name: string) {
  // 1. Submit to HubSpot form
  await submitForm(portalId, signupFormGuid, { email, firstname: name }, {});

  // 2. Find the created contact
  const contact = await findContactByEmail(email);

  // 3. Add to nurture list
  await addToList(nurtureListId, [contact.id]);

  // 4. Create follow-up task
  await createFollowUpTask(
    contact.id,
    `Welcome call: ${name}`,
    new Date(Date.now() + 2 * 86400000), // 2 days
    salesRepOwnerId
  );
}

Resources

Next Steps

For common errors, see hubspot-common-errors.