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.typeCSV 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>&1Full 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 500Full 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 500Next steps
- Export email data to Salesforce — the same workflow targeting Salesforce Leads, Contacts, and Accounts.
- Organize emails by company — group your inbox by sender domain before importing into any CRM.
- Enrich contacts from email — extract job titles, phone numbers, and LinkedIn URLs from email signatures to populate richer Pipedrive Person records.
- Map contact hierarchy from email — identify decision-makers and org charts from email patterns to prioritize deals.
- CRM Email Workflows series — all 8 guides for extracting CRM intelligence from your inbox.
- Full command reference — every Nylas CLI flag and subcommand documented.