Claude-code-plugins hubspot-core-workflow-b
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-b" ~/.claude/skills/jeremylongshore-claude-code-plugins-hubspot-core-workflow-b && rm -rf "$T"
manifest:
plugins/saas-packs/hubspot-pack/skills/hubspot-core-workflow-b/SKILL.mdsource content
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
setuphubspot-install-auth - Scopes:
,crm.lists.read
,crm.lists.write
,content
,formscrm.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 |
|---|---|---|---|
| 404 | Invalid list ID | Verify list exists in HubSpot |
| 404 | Invalid form GUID | Check form ID in Marketing > Forms |
| 400 | Stage not in pipeline | Fetch pipeline stages first |
| 403 | Missing or 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.