Data Quality
TypeScript
Webhook Trigger

Auto-Create & Associate Company from Contact Domain

Extract corporate domain from newly created contacts (excluding freemail), look up or create the matching Company in HubSpot, and link them.

Why this automation is needed

Native HubSpot workflows often struggle with complex multi-object traversals, conditional lookback attribution windows, custom phone format standardizations, and custom scoring calculations.

With Workflood, this recipe runs in a secure gVisor container sandbox with pre-authenticated HubSpot SDK injection, avoiding HubSpot Data Hub execution limits, memory bottlenecks, and per-seat fees.

Action Source Code (TypeScript)

sync-company-domain-enrichment.ts
TypeScript
export default async function main(input: {
  contactId?: string
  email?: string
  firstName?: string
  lastName?: string
  [key: string]: unknown
}): Promise<{
  associated: boolean
  companyId?: string
  domain?: string
  message: string
}> {
  const rawInput = (Array.isArray(input) ? input[0] : input) || {}
  let contactId = (rawInput.contactId || rawInput.contact_id || rawInput.objectId || (rawInput.object && (rawInput.object as Record<string, unknown>).objectId) || rawInput.hs_object_id) as string | undefined
  let email = (rawInput.email || (rawInput.fields && (rawInput.fields as Record<string, unknown>).email) || (rawInput.properties && (rawInput.properties as Record<string, unknown>).email)) as string | undefined

  const freemailDomains = new Set([
    'gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com',
    'aol.com', 'protonmail.com', 'zoho.com', 'mail.com', 'gmx.com',
    'live.com', 'yandex.com', 'me.com', 'msn.com', 'comcast.net'
  ])

  if (contactId && !email) {
    const contact = await hubspot.client.crm.contacts.basicApi.getById(contactId, ['email'])
    email = contact.properties.email
  }

  // Trigger-Agnostic Fallback: if no email or contact given, scan recent contacts
  if (!email) {
    const recentContacts = await hubspot.client.crm.contacts.basicApi.getPage(
      25, 0, ['email', 'firstname', 'lastname']
    )
    let enriched = 0
    for (const c of recentContacts.results) {
      const cEmail = c.properties.email
      if (!cEmail || !cEmail.includes('@')) continue
      const cDomain = cEmail.split('@')[1].toLowerCase().trim()
      if (freemailDomains.has(cDomain)) continue

      const existingCompanies = await hubspot.client.crm.companies.basicApi.getPage(
        1, 0, ['name', 'domain'], undefined, undefined,
        [{ value: cDomain, propertyName: 'domain', operator: 'EQ' }]
      )
      let compId: string
      if (existingCompanies.results.length > 0) {
        compId = existingCompanies.results[0].id
      } else {
        const cName = cDomain.split('.')[0].charAt(0).toUpperCase() + cDomain.split('.')[0].slice(1)
        const newComp = await hubspot.client.crm.companies.basicApi.create({
          properties: { name: cName, domain: cDomain },
        })
        compId = newComp.id
      }
      await hubspot.client.crm.associations.v4.basicApi.create(
        'contacts', c.id, 'companies', compId,
        [{ associationCategory: 'HUBSPOT_DEFINED', associationTypeId: 1 }]
      )
      enriched++
    }
    return {
      associated: enriched > 0,
      message: `Trigger-agnostic scan: enriched & associated ${enriched} contact(s) to company domains`,
    }
  }

  const domain = email.split('@')[1].toLowerCase().trim()
  if (freemailDomains.has(domain)) {
    return {
      associated: false,
      domain,
      message: `Skipped freemail provider domain: ${domain}`,
    }
  }

  // 1. Search for existing Company with this domain
  const existingCompanies = await hubspot.client.crm.companies.basicApi.getPage(
    1,
    0,
    ['name', 'domain'],
    undefined,
    undefined,
    [{ value: domain, propertyName: 'domain', operator: 'EQ' }]
  )

  let companyId: string
  if (existingCompanies.results.length > 0) {
    companyId = existingCompanies.results[0].id
  } else {
    // 2. Create new Company using formatted domain name
    const companyName = domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1)
    const newCompany = await hubspot.client.crm.companies.basicApi.create({
      properties: {
        name: companyName,
        domain: domain,
      },
    })
    companyId = newCompany.id
  }

  // 3. Associate Contact to Company if contactId provided
  if (contactId) {
    await hubspot.client.crm.associations.v4.basicApi.create(
      'contacts',
      contactId,
      'companies',
      companyId,
      [
        {
          associationCategory: 'HUBSPOT_DEFINED',
          associationTypeId: 1,
        },
      ]
    )
  }

  return {
    associated: true,
    companyId,
    domain,
    message: `Successfully linked ${email} to Company ${companyId} (${domain})`,
  }
}

How to deploy in Workflood

1

Connect your HubSpot Portal

Add your HubSpot Private App token once. Workflood securely encrypts it via libsodium XChaCha20 and injects pre-authenticated SDK clients.

2

Create Action from Template

Select this recipe in Workflood. Edit variables, customize logic in Monaco editor, or execute dry runs with sample JSON payloads.

3

Trigger via Webhook or Cron

Trigger from standard HubSpot workflows via Webhook action, or schedule recurring batch syncs with cron expressions and timezone handling.