---
title: "How to Power Outbound Campaigns with CRM Contact Data Using Unified's CRM API"
img: https://s3.us-east-2.amazonaws.com/unified-article-images/how_to_power_outbound_campaigns_with_crm_contact_data_using_unified_crm_api-icon.png
date: 2026-02-02T17:06:00.000Z
tag: Product
description: "Outbound campaigns don't fail because of messaging. They fail because the audience isn't well defined."
url: "https://unified.to/blog/how_to_power_outbound_campaigns_with_crm_contact_data_using_unified_crm_api"
---

# How to Power Outbound Campaigns with CRM Contact Data Using Unified's CRM API
------
_February 2, 2026_

Outbound campaigns don't fail because of messaging. They fail because the audience isn't well defined.


When you support multiple CRMs, 'send an email to our contacts' quickly becomes ambiguous. Contact fields vary. Company context may or may not exist. Updates arrive at different times. If you can't explain _why_ a contact was included in a campaign, it's hard to trust the results—or debug them.


Unified's [CRM API](/crm) lets you build outbound audiences on top of a normalized contact and company model. Instead of syncing everything into a separate system or branching logic per CRM, you can fetch contacts, enrich them with company data, and produce a campaign-ready list using documented fields and predictable [pagination](/docs/reference/pagination).


This guide shows how to build a clean, reusable outbound audience using **contacts and companies only**, with Unified's CRM API and the TypeScript SDK. No campaign tools, no UI, and no assumptions about delivery channels.


### Prerequisites

- Node.js v18+
- A Unified account with a CRM integration enabled
- Your [Unified API](/technology) key
- A customer CRM `connectionId`

## Step 1: Set up your project


```bash
mkdir crm-outbound-audience
cd crm-outbound-audience
npm init -y
npm install @unified-api/typescript-sdk dotenv
```


Create a `.env` file:


```bash
UNIFIED_API_KEY=your_unified_api_key
CONNECTION_CRM=your_customer_crm_connection_id
```


## Step 2: Initialize the SDK


```typescript
import "dotenv/config";
import { UnifiedTo } from "@unified-api/typescript-sdk";

const { UNIFIED_API_KEY, CONNECTION_CRM } = process.env;

if (!UNIFIED_API_KEY) throw new Error("Missing UNIFIED_API_KEY");
if (!CONNECTION_CRM) throw new Error("Missing CONNECTION_CRM");

const sdk = new UnifiedTo({
  security: { jwt: UNIFIED_API_KEY },
});

const connectionId = CONNECTION_CRM;
```


## Step 3: Understand the outbound building blocks


For outbound audience construction, you only need two CRM objects.


### Contacts (`CrmContact`)


Contacts represent people you can reach. Useful fields include:

- identity: `id`, `name`, `first_name`, `last_name`, `title`, `department`
- contact methods:
    - `emails[]` with `type` (`WORK`, `HOME`, `OTHER`)
    - `telephones[]` with `type` (`WORK`, `HOME`, `OTHER`, `FAX`, `MOBILE`)
- company linkage: `company` (string), `company_ids[]`
- timestamps: `created_at`, `updated_at`
- enrichment: `link_urls[]`, `metadata[]`
- `raw` for CRM-specific data if needed

### Companies (`CrmCompany`)


Companies provide firmographic context for targeting and personalization.


Commonly used fields:

- identity: `id`, `name`
- firmographics: `industry`, `employees`, `is_active`, `timezone`
- domains: `domains[]`
- enrichment: `tags[]`, `metadata[]`
- relationships: `contact_ids[]`
- timestamps: `created_at`, `updated_at`
- `raw` when CRM-specific data is required

Required fields and write support vary by CRM. Always verify field support for your integration in the **Feature Support** tab.


## Step 4: Fetch contacts safely (pagination + freshness)


The contact list endpoint supports pagination and incremental filtering:

- `limit` defaults to 100
- `offset` is zero-based
- stop paging when returned results are fewer than `limit`
- use `updated_gte` to fetch only recently changed contacts

```typescript
type Sort = "name" | "updated_at" | "created_at";
type Order = "asc" | "desc";

async function fetchAllContacts(opts?: {
  pageSize?: number;
  updated_gte?: string;
  sort?: Sort;
  order?: Order;
  query?: string;
}) {
  const pageSize = opts?.pageSize ?? 100;
  let offset = 0;
  const out: any[] = [];

  while (true) {
    const page = await sdk.crm.listCrmContacts({
      connectionId,
      limit: pageSize,
      offset,
      updated_gte: opts?.updated_gte ?? "",
      sort: opts?.sort ?? "updated_at",
      order: opts?.order ?? "asc",
      query: opts?.query ?? "",
    });

    if (!page || page.length === 0) break;

    out.push(...page);
    if (page.length < pageSize) break;

    offset += pageSize;
  }

  return out;
}
```


This pattern keeps outbound audiences:

- explainable
- reproducible
- incremental

## Step 5: Fetch companies for enrichment


Contacts often reference companies by ID. To enrich your audience with firmographic data, fetch companies and build a lookup map.


```typescript
async function fetchAllCompanies(opts?: {
  pageSize?: number;
  updated_gte?: string;
}) {
  const pageSize = opts?.pageSize ?? 100;
  let offset = 0;
  const out: any[] = [];

  while (true) {
    const page = await sdk.crm.listCrmCompanies({
      connectionId,
      limit: pageSize,
      offset,
      updated_gte: opts?.updated_gte ?? "",
      sort: "updated_at",
      order: "asc",
    });

    if (!page || page.length === 0) break;

    out.push(...page);
    if (page.length < pageSize) break;

    offset += pageSize;
  }

  return out;
}

function indexById(rows: any[]): Record<string, any> {
  const out: Record<string, any> = {};
  for (const r of rows) {
    if (!r?.id) continue;
    out[String(r.id)] = r;
  }
  return out;
}
```


## Step 6: Define an outbound audience row


An outbound audience row should be:

- deterministic
- built only from documented fields
- explicit about missing data
- easy to inspect and debug

Here's a conservative, campaign-ready shape:


```typescript
type OutboundContact = {
  contact_id: string;

  first_name?: string;
  last_name?: string;
  title?: string;
  department?: string;

  primary_email?: string;
  email_type?: string;
  primary_phone?: string;

  company_id?: string;
  company_name?: string;
  company_industry?: string;
  company_employees?: number;
  company_is_active?: boolean;
  company_domains?: string[];

  last_updated_at?: string;
};
```


## Step 7: Build the outbound audience


This step joins contacts to companies and extracts the fields needed for outreach and segmentation.


```typescript
function buildOutboundAudience(input: {
  contacts: any[];
  companiesById: Record<string, any>;
}): OutboundContact[] {
  const { contacts, companiesById } = input;
  const out: OutboundContact[] = [];

  for (const c of contacts) {
    if (!c?.id) continue;

    const email = c.emails?.[0];
    const phone = c.telephones?.[0];

    const companyId = c.company_ids?.[0];
    const company = companyId ? companiesById[companyId] : undefined;

    out.push({
      contact_id: c.id,

      first_name: c.first_name,
      last_name: c.last_name,
      title: c.title,
      department: c.department,

      primary_email: email?.email,
      email_type: email?.type,
      primary_phone: phone?.telephone,

      company_id: companyId,
      company_name: company?.name ?? c.company,
      company_industry: company?.industry,
      company_employees: company?.employees,
      company_is_active: company?.is_active,
      company_domains: company?.domains,

      last_updated_at: c.updated_at,
    });
  }

  return out;
}
```


This produces an audience you can:

- filter by firmographics
- segment by role or department
- personalize messaging
- audit when contacts were last updated

## Step 8: Segment the audience for campaigns


Once you have a normalized audience, segmentation becomes straightforward and CRM-agnostic.


Examples:


```typescript
const enterpriseEngineeringContacts = audience.filter(
  (c) =>
    c.company_employees !== undefined &&
    c.company_employees >= 1000 &&
    c.department === "Engineering"
);

const activeCompanyContacts = audience.filter(
  (c) => c.company_is_active === true
);
```


These filters behave consistently across CRMs because they're applied _after_ normalization.


## Step 9: Use the audience in outbound workflows


Unified provides the audience. Delivery happens elsewhere.


From here, you can:

- send contacts to an email provider
- sync to a sales engagement tool
- enqueue jobs for SMS or call workflows
- generate CSVs for review or approval

Keep delivery concerns separate from audience construction. It makes campaigns easier to reason about and debug.


## Summary


Using Unified's CRM API, you can power outbound campaigns with a clean, explainable audience built from normalized contact and company data:

- Fetch contacts incrementally using `updated_gte`
- Enrich with firmographic context from companies
- Build deterministic, inspectable audience rows
- Segment without CRM-specific logic
- Reuse the same pipeline across customers and CRMs

By separating **audience definition** from **message delivery**, you keep outbound systems flexible, auditable, and easier to maintain as you add new CRM integrations.


→ [Start your 30-day free trial](https://app.unified.to/login)


→ [Book a demo](https://calendly.com/d/cph9-g8n-jzg/connect-with-unified)