Guide

Export Email Data to Pipedrive

Pipedrive is a deal-centric CRM built for salespeople. Every deal flows through a visual pipeline, and the contacts behind those deals live as Persons and Organizations. This guide shows you how to extract email and contact data from Nylas CLI and push it into Pipedrive — so your pipeline reflects reality instead of manual data entry.

Why sync email data to Pipedrive

Pipedrive organizes everything around deals. A deal moves through pipeline stages, and every deal is connected to a Person and an Organization. But deals don't create themselves — salespeople spend hours manually entering contacts they already emailed, logging activities that already happened, and linking organizations they already know.

Your inbox already has the data Pipedrive needs. Every email contains a sender name, email address, company domain, subject line, and timestamp. That maps directly to Pipedrive's Person, Organization, and Activity objects. Instead of typing it in, you export it.

The Nylas CLI gives you structured JSON output for emails and contacts across Gmail, Outlook, Exchange, and IMAP — all with a single command. Pipe that into Pipedrive's CSV import or REST API, and your CRM reflects your actual communication history.

Export data from Nylas CLI

Start by pulling your recent contacts and emails as JSON. The --json flag gives you structured output that you can pipe into jq, Python, or any other tool.

# Export your contacts
nylas contacts list --json --limit 200 | jq '.' > contacts.json

# Export recent emails
nylas email list --json --limit 500 | jq '.' > emails.json

# Preview a contact record
nylas contacts list --json --limit 1 | jq '.[0]'

A contact record from Nylas includes the fields Pipedrive needs:

{
  "id": "abc123",
  "given_name": "Sarah",
  "surname": "Chen",
  "emails": [
    { "email": "sarah@acme.com", "type": "work" }
  ],
  "phone_numbers": [
    { "number": "+1-555-0142", "type": "work" }
  ],
  "company_name": "Acme Corp"
}

An email record gives you activity data:

{
  "id": "msg456",
  "subject": "Q3 proposal follow-up",
  "from": [{ "name": "Sarah Chen", "email": "sarah@acme.com" }],
  "to": [{ "name": "You", "email": "you@company.com" }],
  "date": 1710000000
}

Pipedrive object model mapping

Pipedrive has four core objects that map to email data. Understanding these mappings is the key to a clean import.

Person — an individual contact. Each Person has a name (string), an email field (array of email objects), and a phone field (array of phone objects). Persons are linked to Organizations and Deals.

Organization — a company. Has a name field. Persons belong to Organizations. You can derive the Organization name from the email domain (e.g.,acme.com becomes “Acme”).

Deal — an opportunity in your pipeline. Deals link to a Person (primary contact) and an Organization. You create deals for contacts who represent active opportunities.

Activity — a logged event like an email, call, or meeting. Activities have a type field (use "email"), a subject, a due_date, and can be linked to a Person, Organization, or Deal.

Here is how Nylas fields map to Pipedrive:

# Nylas Contact → Pipedrive Person
given_name + surname Person.name
emails[].email Person.email (array of {value, primary, label})
phone_numbers[].number Person.phone (array of {value, primary, label})
company_name Person.org_id (link to Organization)

# Nylas Contact → Pipedrive Organization
company_name Organization.name
emails[].email domain Organization.name (fallback)

# Nylas Email → Pipedrive Activity
subject Activity.subject
date Activity.due_date (ISO format)
from/to Activity.person_id (link to Person)
"email" Activity.type

CSV import via Pipedrive Import tool

The simplest approach is to generate a CSV and use Pipedrive's built-in Import tool. This works well for an initial bulk import of contacts.

# Generate a Pipedrive-ready CSV from Nylas contacts
nylas contacts list --json --limit 500 | jq -r '
  ["Person - Name","Person - Email","Person - Phone","Organization - Name"],
  (.[] | [
    ((.given_name // "") + " " + (.surname // "") | gsub("^\s+|\s+$"; "")),
    (.emails[0].email // ""),
    (.phone_numbers[0].number // ""),
    (.company_name // (.emails[0].email // "" | split("@")[1] // ""))
  ])
  | @csv' > pipedrive-contacts.csv

echo "Wrote $(wc -l < pipedrive-contacts.csv) rows to pipedrive-contacts.csv"

The CSV header uses Pipedrive's import column naming convention. When you upload this file in Pipedrive under Settings → Import data, the columns will auto-map to the correct fields.

For email activities, generate a separate CSV:

# Generate activity CSV from emails
nylas email list --json --limit 500 | jq -r '
  ["Activity - Subject","Activity - Type","Activity - Due date","Person - Email"],
  (.[] | [
    .subject,
    "email",
    (.date | todate | split("T")[0]),
    .from[0].email
  ])
  | @csv' > pipedrive-activities.csv

echo "Wrote $(wc -l < pipedrive-activities.csv) rows to pipedrive-activities.csv"

Import contacts first, then activities. Pipedrive matches activities to existing Persons by email address, so the Person records need to exist before you import activities.

Pipedrive REST API import

For automated or incremental imports, use Pipedrive's REST API v1. You need an API token from Settings → Personal preferences → API in your Pipedrive account.

Create an Organization:

# Create an Organization in Pipedrive
curl -s -X POST "https://api.pipedrive.com/v1/organizations?api_token=$PIPEDRIVE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp"
  }' | jq '.data.id'

Create a Person linked to an Organization:

# Create a Person in Pipedrive
# org_id is the Organization ID returned above
curl -s -X POST "https://api.pipedrive.com/v1/persons?api_token=$PIPEDRIVE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sarah Chen",
    "email": [{"value": "sarah@acme.com", "primary": true, "label": "work"}],
    "phone": [{"value": "+1-555-0142", "primary": true, "label": "work"}],
    "org_id": 123
  }' | jq '.data.id'

Log an email Activity:

# Log an email activity linked to a Person
curl -s -X POST "https://api.pipedrive.com/v1/activities?api_token=$PIPEDRIVE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Q3 proposal follow-up",
    "type": "email",
    "due_date": "2026-03-10",
    "due_time": "14:30",
    "person_id": 456,
    "org_id": 123,
    "done": 1,
    "note": "Discussed pricing and timeline for Q3 rollout."
  }' | jq '.data.id'

The done: 1 flag marks the activity as completed, which is correct for emails that already happened. Use done: 0 for follow-up tasks.

Search before creating to avoid duplicates. Pipedrive's search endpoint lets you find existing Persons by email:

# Search for an existing Person by email
curl -s "https://api.pipedrive.com/v1/persons/search?term=sarah@acme.com&fields=email&api_token=$PIPEDRIVE_API_TOKEN" \
  | jq '.data.items[0].item.id'

Link contacts and activities to Deals

Contacts and activities are useful on their own, but the real power of Pipedrive is the deal pipeline. Once you have Persons and Organizations imported, you can create Deals and link them.

# Create a Deal linked to a Person and Organization
curl -s -X POST "https://api.pipedrive.com/v1/deals?api_token=$PIPEDRIVE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Acme Corp - Q3 Engagement",
    "person_id": 456,
    "org_id": 123,
    "stage_id": 1,
    "value": 25000,
    "currency": "USD"
  }' | jq '.data.id'

The stage_id determines where the deal appears in your pipeline. You can list available stages with:

# List pipeline stages
curl -s "https://api.pipedrive.com/v1/stages?api_token=$PIPEDRIVE_API_TOKEN" \
  | jq '.data[] | {id, name, pipeline_name: .pipeline_name}'

Once you have a Deal ID, link activities to it by including deal_id in the activity creation request. This gives you a full email timeline on each deal.

# Log an email activity linked to a Deal
curl -s -X POST "https://api.pipedrive.com/v1/activities?api_token=$PIPEDRIVE_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Contract review",
    "type": "email",
    "due_date": "2026-03-12",
    "person_id": 456,
    "org_id": 123,
    "deal_id": 789,
    "done": 1
  }' | jq '.data.id'

Scheduled sync script

A one-time import is a start, but you want ongoing sync. This Bash script runs on a schedule (cron or CI), exports new emails from the last 24 hours, and pushes them into Pipedrive as activities linked to the correct Persons.

#!/usr/bin/env bash
# sync-pipedrive.sh — Push new email activity to Pipedrive daily
# Usage: ./sync-pipedrive.sh
# Schedule with: crontab -e → 0 8 * * * /path/to/sync-pipedrive.sh

set -euo pipefail

API_TOKEN="${PIPEDRIVE_API_TOKEN:?Set PIPEDRIVE_API_TOKEN}"
BASE_URL="https://api.pipedrive.com/v1"

# Export recent emails from Nylas CLI
EMAILS=$(nylas email list --json --limit 100)

echo "$EMAILS" | jq -c '.[]' | while read -r msg; do
  SUBJECT=$(echo "$msg" | jq -r '.subject // "No subject"')
  FROM_EMAIL=$(echo "$msg" | jq -r '.from[0].email // empty')
  FROM_NAME=$(echo "$msg" | jq -r '.from[0].name // empty')
  DATE=$(echo "$msg" | jq -r '.date | todate | split("T")[0]')

  [ -z "$FROM_EMAIL" ] && continue

  # Search for existing Person by email
  PERSON_ID=$(curl -s "$BASE_URL/persons/search?term=$FROM_EMAIL&fields=email&api_token=$API_TOKEN" \
    | jq -r '.data.items[0].item.id // empty')

  # Create Person if not found
  if [ -z "$PERSON_ID" ]; then
    DOMAIN=$(echo "$FROM_EMAIL" | cut -d@ -f2)
    ORG_NAME=$(echo "$DOMAIN" | cut -d. -f1 | awk '{print toupper(substr($0,1,1)) substr($0,2)}')

    # Create or find Organization
    ORG_ID=$(curl -s "$BASE_URL/organizations/search?term=$ORG_NAME&api_token=$API_TOKEN" \
      | jq -r '.data.items[0].item.id // empty')

    if [ -z "$ORG_ID" ]; then
      ORG_ID=$(curl -s -X POST "$BASE_URL/organizations?api_token=$API_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"name\": \"$ORG_NAME\"}" \
        | jq -r '.data.id')
    fi

    PERSON_ID=$(curl -s -X POST "$BASE_URL/persons?api_token=$API_TOKEN" \
      -H "Content-Type: application/json" \
      -d "{\"name\": \"$FROM_NAME\", \"email\": [{\"value\": \"$FROM_EMAIL\", \"primary\": true}], \"org_id\": $ORG_ID}" \
      | jq -r '.data.id')

    echo "Created Person: $FROM_NAME ($PERSON_ID)"
  fi

  # Log email activity
  curl -s -X POST "$BASE_URL/activities?api_token=$API_TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"subject\": \"$SUBJECT\", \"type\": \"email\", \"due_date\": \"$DATE\", \"person_id\": $PERSON_ID, \"done\": 1}" \
    > /dev/null

  echo "Logged activity: $SUBJECT → Person $PERSON_ID"
done

echo "Sync complete."

Add it to cron to run daily at 8 AM:

# Run daily at 8:00 AM
0 8 * * * PIPEDRIVE_API_TOKEN=your_token /path/to/sync-pipedrive.sh >> /var/log/pipedrive-sync.log 2>&1

Full Python version

A complete Python script that exports contacts and emails from Nylas CLI, creates or updates Persons and Organizations in Pipedrive, and logs email activities. Uses therequests library for API calls.

#!/usr/bin/env python3
"""Export email data from Nylas CLI to Pipedrive CRM.

Usage:
    export PIPEDRIVE_API_TOKEN=your_token
    python3 sync_pipedrive.py [--contacts 200] [--emails 500]
"""

import argparse
import json
import os
import subprocess
import sys
from urllib.parse import urlencode

import requests

BASE_URL = "https://api.pipedrive.com/v1"
API_TOKEN = os.environ.get("PIPEDRIVE_API_TOKEN", "")


def api_url(path: str, **params: str) -> str:
    """Build a Pipedrive API URL with the api_token."""
    params["api_token"] = API_TOKEN
    return f"{BASE_URL}/{path}?{urlencode(params)}"


def fetch_contacts(limit: int = 200) -> list[dict]:
    """Export contacts from Nylas CLI."""
    result = subprocess.run(
        ["nylas", "contacts", "list", "--json", "--limit", str(limit)],
        capture_output=True, text=True, check=True,
    )
    return json.loads(result.stdout)


def fetch_emails(limit: int = 500) -> list[dict]:
    """Export emails from Nylas CLI."""
    result = subprocess.run(
        ["nylas", "email", "list", "--json", "--limit", str(limit)],
        capture_output=True, text=True, check=True,
    )
    return json.loads(result.stdout)


def search_person(email: str) -> int | None:
    """Search for an existing Person by email. Returns ID or None."""
    resp = requests.get(api_url("persons/search", term=email, fields="email"))
    resp.raise_for_status()
    items = resp.json().get("data", {}).get("items", [])
    if items:
        return items[0]["item"]["id"]
    return None


def search_organization(name: str) -> int | None:
    """Search for an existing Organization by name. Returns ID or None."""
    resp = requests.get(api_url("organizations/search", term=name))
    resp.raise_for_status()
    items = resp.json().get("data", {}).get("items", [])
    if items:
        return items[0]["item"]["id"]
    return None


def create_organization(name: str) -> int:
    """Create an Organization in Pipedrive. Returns the new ID."""
    resp = requests.post(api_url("organizations"), json={"name": name})
    resp.raise_for_status()
    return resp.json()["data"]["id"]


def create_person(
    name: str,
    email: str,
    phone: str | None = None,
    org_id: int | None = None,
) -> int:
    """Create a Person in Pipedrive. Returns the new ID."""
    payload: dict = {
        "name": name,
        "email": [{"value": email, "primary": True, "label": "work"}],
    }
    if phone:
        payload["phone"] = [{"value": phone, "primary": True, "label": "work"}]
    if org_id:
        payload["org_id"] = org_id
    resp = requests.post(api_url("persons"), json=payload)
    resp.raise_for_status()
    return resp.json()["data"]["id"]


def create_activity(
    subject: str,
    due_date: str,
    person_id: int | None = None,
    org_id: int | None = None,
    deal_id: int | None = None,
) -> int:
    """Log an email activity in Pipedrive. Returns the new ID."""
    payload: dict = {
        "subject": subject,
        "type": "email",
        "due_date": due_date,
        "done": 1,
    }
    if person_id:
        payload["person_id"] = person_id
    if org_id:
        payload["org_id"] = org_id
    if deal_id:
        payload["deal_id"] = deal_id
    resp = requests.post(api_url("activities"), json=payload)
    resp.raise_for_status()
    return resp.json()["data"]["id"]


def org_name_from_domain(domain: str) -> str:
    """Derive an Organization name from an email domain."""
    name = domain.split(".")[0]
    return name[0].upper() + name[1:] if name else domain


def sync_contacts(contacts: list[dict]) -> dict[str, int]:
    """Import contacts as Persons and Organizations. Returns email→person_id map."""
    person_map: dict[str, int] = {}

    for contact in contacts:
        emails = contact.get("emails", [])
        if not emails:
            continue

        email = emails[0].get("email", "")
        if not email or "@" not in email:
            continue

        # Skip if already imported
        person_id = search_person(email)
        if person_id:
            person_map[email] = person_id
            print(f"  Found existing Person: {email} (ID {person_id})")
            continue

        # Build name
        given = contact.get("given_name", "")
        surname = contact.get("surname", "")
        name = f"{given} {surname}".strip() or email.split("@")[0]

        # Find or create Organization
        company = contact.get("company_name", "")
        domain = email.split("@")[1]
        org_name = company or org_name_from_domain(domain)
        org_id = search_organization(org_name) or create_organization(org_name)

        # Get phone number
        phones = contact.get("phone_numbers", [])
        phone = phones[0].get("number") if phones else None

        # Create Person
        person_id = create_person(name, email, phone, org_id)
        person_map[email] = person_id
        print(f"  Created Person: {name} <{email}> (ID {person_id}) → Org {org_name}")

    return person_map


def sync_emails(emails: list[dict], person_map: dict[str, int]) -> int:
    """Log emails as Activities linked to Persons. Returns count."""
    count = 0

    for msg in emails:
        from_field = msg.get("from", [{}])[0]
        from_email = from_field.get("email", "")
        subject = msg.get("subject", "No subject")

        # Convert Unix timestamp to ISO date
        date_ts = msg.get("date", 0)
        if isinstance(date_ts, (int, float)) and date_ts > 0:
            from datetime import datetime, timezone
            due_date = datetime.fromtimestamp(date_ts, tz=timezone.utc).strftime("%Y-%m-%d")
        else:
            continue

        person_id = person_map.get(from_email)
        if not person_id:
            # Try to find the person in Pipedrive
            person_id = search_person(from_email)

        if not person_id:
            continue

        create_activity(subject, due_date, person_id=person_id)
        count += 1
        print(f"  Logged activity: {subject[:60]} → Person {person_id}")

    return count


def main() -> None:
    if not API_TOKEN:
        print("Error: Set PIPEDRIVE_API_TOKEN environment variable.", file=sys.stderr)
        sys.exit(1)

    parser = argparse.ArgumentParser(description="Sync Nylas email data to Pipedrive")
    parser.add_argument("--contacts", type=int, default=200, help="Number of contacts to export")
    parser.add_argument("--emails", type=int, default=500, help="Number of emails to export")
    args = parser.parse_args()

    print(f"Exporting {args.contacts} contacts from Nylas CLI...")
    contacts = fetch_contacts(args.contacts)
    print(f"  Got {len(contacts)} contacts")

    print("\nSyncing contacts to Pipedrive...")
    person_map = sync_contacts(contacts)
    print(f"  Synced {len(person_map)} persons")

    print(f"\nExporting {args.emails} emails from Nylas CLI...")
    emails = fetch_emails(args.emails)
    print(f"  Got {len(emails)} emails")

    print("\nLogging email activities to Pipedrive...")
    activity_count = sync_emails(emails, person_map)
    print(f"  Logged {activity_count} activities")

    print("\nDone.")


if __name__ == "__main__":
    main()

Run it:

export PIPEDRIVE_API_TOKEN=your_api_token_here
python3 sync_pipedrive.py --contacts 200 --emails 500

Full TypeScript version

The same workflow in TypeScript, using the built-in fetch API (Node 18+). Typed interfaces ensure your Pipedrive payloads match the API schema.

#!/usr/bin/env npx tsx
/**
 * Export email data from Nylas CLI to Pipedrive CRM.
 *
 * Usage:
 *   export PIPEDRIVE_API_TOKEN=your_token
 *   npx tsx sync_pipedrive.ts [--contacts 200] [--emails 500]
 */

import { execFileSync } from "child_process";

const BASE_URL = "https://api.pipedrive.com/v1";
const API_TOKEN = process.env.PIPEDRIVE_API_TOKEN ?? "";

if (!API_TOKEN) {
  console.error("Error: Set PIPEDRIVE_API_TOKEN environment variable.");
  process.exit(1);
}

// --- Pipedrive types ---

interface PipedriveEmail {
  value: string;
  primary: boolean;
  label: string;
}

interface PipedrivePhone {
  value: string;
  primary: boolean;
  label: string;
}

interface PersonPayload {
  name: string;
  email: PipedriveEmail[];
  phone?: PipedrivePhone[];
  org_id?: number;
}

interface ActivityPayload {
  subject: string;
  type: string;
  due_date: string;
  done: number;
  person_id?: number;
  org_id?: number;
  deal_id?: number;
}

// --- Nylas types ---

interface NylasContact {
  given_name?: string;
  surname?: string;
  emails?: { email: string; type?: string }[];
  phone_numbers?: { number: string; type?: string }[];
  company_name?: string;
}

interface NylasEmail {
  subject?: string;
  from?: { name?: string; email: string }[];
  to?: { name?: string; email: string }[];
  date?: number;
}

// --- API helpers ---

function apiUrl(path: string, params: Record<string, string> = {}): string {
  const query = new URLSearchParams({ ...params, api_token: API_TOKEN });
  return `${BASE_URL}/${path}?${query}`;
}

async function searchPerson(email: string): Promise<number | null> {
  const resp = await fetch(apiUrl("persons/search", { term: email, fields: "email" }));
  const data = await resp.json();
  const items = data?.data?.items ?? [];
  return items.length > 0 ? items[0].item.id : null;
}

async function searchOrganization(name: string): Promise<number | null> {
  const resp = await fetch(apiUrl("organizations/search", { term: name }));
  const data = await resp.json();
  const items = data?.data?.items ?? [];
  return items.length > 0 ? items[0].item.id : null;
}

async function createOrganization(name: string): Promise<number> {
  const resp = await fetch(apiUrl("organizations"), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name }),
  });
  const data = await resp.json();
  return data.data.id;
}

async function createPerson(payload: PersonPayload): Promise<number> {
  const resp = await fetch(apiUrl("persons"), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const data = await resp.json();
  return data.data.id;
}

async function createActivity(payload: ActivityPayload): Promise<number> {
  const resp = await fetch(apiUrl("activities"), {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
  const data = await resp.json();
  return data.data.id;
}

// --- CLI helpers ---

function fetchContacts(limit: number): NylasContact[] {
  const output = execFileSync("nylas", ["contacts", "list", "--json", "--limit", String(limit)], {
    encoding: "utf-8",
    maxBuffer: 50 * 1024 * 1024,
  });
  return JSON.parse(output);
}

function fetchEmails(limit: number): NylasEmail[] {
  const output = execFileSync("nylas", ["email", "list", "--json", "--limit", String(limit)], {
    encoding: "utf-8",
    maxBuffer: 50 * 1024 * 1024,
  });
  return JSON.parse(output);
}

function orgNameFromDomain(domain: string): string {
  const name = domain.split(".")[0];
  return name.charAt(0).toUpperCase() + name.slice(1);
}

// --- Sync logic ---

async function syncContacts(contacts: NylasContact[]): Promise<Map<string, number>> {
  const personMap = new Map<string, number>();

  for (const contact of contacts) {
    const email = contact.emails?.[0]?.email;
    if (!email || !email.includes("@")) continue;

    // Check for existing Person
    const existingId = await searchPerson(email);
    if (existingId) {
      personMap.set(email, existingId);
      console.log(`  Found existing Person: ${email} (ID ${existingId})`);
      continue;
    }

    // Build name
    const given = contact.given_name ?? "";
    const surname = contact.surname ?? "";
    const name = `${given} ${surname}`.trim() || email.split("@")[0];

    // Find or create Organization
    const domain = email.split("@")[1];
    const orgName = contact.company_name || orgNameFromDomain(domain);
    const orgId = (await searchOrganization(orgName)) ?? (await createOrganization(orgName));

    // Build Person payload
    const payload: PersonPayload = {
      name,
      email: [{ value: email, primary: true, label: "work" }],
      org_id: orgId,
    };

    const phone = contact.phone_numbers?.[0]?.number;
    if (phone) {
      payload.phone = [{ value: phone, primary: true, label: "work" }];
    }

    const personId = await createPerson(payload);
    personMap.set(email, personId);
    console.log(`  Created Person: ${name} <${email}> (ID ${personId}) → Org ${orgName}`);
  }

  return personMap;
}

async function syncEmails(emails: NylasEmail[], personMap: Map<string, number>): Promise<number> {
  let count = 0;

  for (const msg of emails) {
    const fromEmail = msg.from?.[0]?.email;
    const subject = msg.subject ?? "No subject";

    if (!fromEmail || !msg.date) continue;

    // Convert Unix timestamp to ISO date
    const dueDate = new Date(msg.date * 1000).toISOString().split("T")[0];

    let personId = personMap.get(fromEmail) ?? null;
    if (!personId) {
      personId = await searchPerson(fromEmail);
    }
    if (!personId) continue;

    await createActivity({
      subject,
      type: "email",
      due_date: dueDate,
      done: 1,
      person_id: personId,
    });

    count++;
    console.log(`  Logged activity: ${subject.slice(0, 60)} → Person ${personId}`);
  }

  return count;
}

// --- Main ---

async function main(): Promise<void> {
  const contactLimit = Number(process.argv.find((_, i, a) => a[i - 1] === "--contacts") ?? 200);
  const emailLimit = Number(process.argv.find((_, i, a) => a[i - 1] === "--emails") ?? 500);

  console.log(`Exporting ${contactLimit} contacts from Nylas CLI...`);
  const contacts = fetchContacts(contactLimit);
  console.log(`  Got ${contacts.length} contacts`);

  console.log("\nSyncing contacts to Pipedrive...");
  const personMap = await syncContacts(contacts);
  console.log(`  Synced ${personMap.size} persons`);

  console.log(`\nExporting ${emailLimit} emails from Nylas CLI...`);
  const emails = fetchEmails(emailLimit);
  console.log(`  Got ${emails.length} emails`);

  console.log("\nLogging email activities to Pipedrive...");
  const activityCount = await syncEmails(emails, personMap);
  console.log(`  Logged ${activityCount} activities`);

  console.log("\nDone.");
}

main();

Run it:

export PIPEDRIVE_API_TOKEN=your_api_token_here
npx tsx sync_pipedrive.ts --contacts 200 --emails 500

Next steps