Guide
Export Email Data to Salesforce
Your inbox contains relationship data that Salesforce never sees — sender domains, CC patterns, reply frequency, meeting context. This guide shows how to export email and contact data from Nylas CLI and import it into Salesforce as Leads, Contacts, Accounts, and Tasks.
Why sync email data to Salesforce
Email reveals relationships that CRM data entry misses. Every email exchange is an implicit activity log — who talked to whom, when, about what, and how often. Salesforce captures none of this unless someone manually logs it, and manual logging has a compliance rate near zero for most sales teams.
Automatically syncing email data to Salesforce gives you three things. First, auto-logged activities: every email becomes a Task record linked to the right Contact or Lead, giving managers visibility into deal engagement without asking reps to log calls. Second, enriched records: email signatures contain phone numbers, job titles, and company names that fill in blank CRM fields. Third, relationship intelligence: CC patterns and reply frequency reveal who the real decision-makers are, which accounts are going cold, and where single-threaded risk exists.
The Nylas CLI makes the export side trivial. One command gives you structured JSON for emails or contacts. The rest of this guide focuses on mapping that data to Salesforce objects and getting it in.
Export from Nylas CLI
Start by exporting your email and contact data as JSON. These two commands give you everything you need:
# Export recent emails (sender, subject, date, body snippet)
nylas email list --json --limit 500 > emails.json
# Export contacts (name, email, phone, company)
nylas contacts list --json --limit 500 > contacts.json
# Preview the structure
cat contacts.json | jq '.[0]'The email export includes from, to, cc, subject, date, and body fields. The contact export includes given_name, surname, emails, phone_numbers, and company_name.
For a single email with full body content, use the read command:
# Read a specific email by ID
nylas email read abc123def --json | jq '{
from: .from[0],
to: .to,
subject: .subject,
date: .date,
body: .body
}'Salesforce object model mapping
Salesforce organizes data into standard objects. Here is how Nylas CLI output maps to each one:
- Contact — a person you have a relationship with.
given_name→FirstName,surname→LastName,emails[0].email→Email,phone_numbers[0].number→Phone. - Lead — a prospect not yet qualified. Same name and email mapping as Contact, plus
company_name→Company. - Account — a company. Derive from the email domain: domain →
Name(orWebsite). - Task — an activity record. Map email subject →
Subject, date →ActivityDate, and setTypeto"Email"andStatusto"Completed".
The key relationship: each Contact belongs to an Account (via AccountId), and Tasks are linked to a Contact or Lead via WhoId. When you import, create Accounts first, then Contacts, then Tasks.
CSV import via Data Loader
The simplest way to get data into Salesforce is CSV import using Data Loader or the built-in Data Import Wizard. Use jq to transform Nylas CLI output into Salesforce-compatible CSV format.
Contacts CSV
# Transform contacts.json into Salesforce Contact CSV
cat contacts.json | jq -r '
["FirstName","LastName","Email","Phone","Title","MailingStreet","MailingCity","MailingState","MailingPostalCode"],
(.[] | [
(.given_name // ""),
(.surname // ""),
((.emails // [])[0].email // ""),
((.phone_numbers // [])[0].number // ""),
(.job_title // ""),
((.physical_addresses // [])[0].street_address // ""),
((.physical_addresses // [])[0].city // ""),
((.physical_addresses // [])[0].state // ""),
((.physical_addresses // [])[0].postal_code // "")
])
| @csv' > salesforce_contacts.csv
echo "Wrote $(wc -l < salesforce_contacts.csv) rows to salesforce_contacts.csv"Leads CSV from email senders
If you do not have contacts but have email data, extract leads directly from email senders:
# Extract unique senders from emails and format as Salesforce Lead CSV
cat emails.json | jq -r '
# Deduplicate by email address
[.[] | .from[0] | select(.email != null)]
| group_by(.email)
| map(.[0])
| map({
email: .email,
name: (.name // ""),
domain: (.email | split("@")[1])
})
| . as $senders
| ["FirstName","LastName","Email","Company","LeadSource"],
($senders[] | [
(.name | split(" ")[0] // ""),
(.name | split(" ")[1:] | join(" ") // ""),
.email,
.domain,
"Email"
])
| @csv' > salesforce_leads.csv
echo "Wrote $(wc -l < salesforce_leads.csv) rows to salesforce_leads.csv"Email activities as Tasks CSV
# Transform emails into Salesforce Task CSV
cat emails.json | jq -r '
["Subject","ActivityDate","Status","Priority","Type","Description","WhoId_Email__c"],
(.[] | [
.subject,
(.date | split("T")[0]),
"Completed",
"Normal",
"Email",
(.body[:200] // ""),
(.from[0].email // "")
])
| @csv' > salesforce_tasks.csv
echo "Wrote $(wc -l < salesforce_tasks.csv) rows to salesforce_tasks.csv"To import these CSVs, open Salesforce → Setup → Data Import Wizard (for Leads and Contacts) or install Salesforce Data Loader for bulk operations. Map the CSV columns to Salesforce fields, choose "Upsert" by Email to avoid duplicates, and run the import.
REST API import
For programmatic imports, use the Salesforce REST API. You need a Connected App with OAuth2 credentials and an access token. The examples below use curl to create records.
Authenticate
# Get an access token using the username-password flow
# Replace with your Connected App credentials
SF_TOKEN=$(curl -s https://login.salesforce.com/services/oauth2/token \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN" \
| jq -r '.access_token')
SF_INSTANCE=$(curl -s https://login.salesforce.com/services/oauth2/token \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN" \
| jq -r '.instance_url')
echo "Authenticated to $SF_INSTANCE"Create a Contact
# Create a Salesforce Contact from Nylas contact data
CONTACT=$(cat contacts.json | jq '.[0]')
curl -s -X POST "$SF_INSTANCE/services/data/v59.0/sobjects/Contact" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d "$(echo $CONTACT | jq '{
FirstName: (.given_name // ""),
LastName: (.surname // "Unknown"),
Email: ((.emails // [])[0].email // ""),
Phone: ((.phone_numbers // [])[0].number // ""),
Title: (.job_title // "")
}')" | jq .
# Response includes the new record ID
# {"id": "003xx000004TmiQAAS", "success": true, "errors": []}Log an email as a Task
# Log an email as a completed Task linked to the Contact
EMAIL=$(cat emails.json | jq '.[0]')
CONTACT_ID="003xx000004TmiQAAS" # from the Contact creation response
curl -s -X POST "$SF_INSTANCE/services/data/v59.0/sobjects/Task" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d "$(echo $EMAIL | jq --arg who "$CONTACT_ID" '{
WhoId: $who,
Subject: .subject,
ActivityDate: (.date | split("T")[0]),
Status: "Completed",
Priority: "Normal",
Type: "Email",
Description: (.body[:500] // "")
}')" | jq .Create an Account from domain
# Create an Account from a sender domain
DOMAIN="acme.com"
curl -s -X POST "$SF_INSTANCE/services/data/v59.0/sobjects/Account" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d "{
"Name": "$(echo $DOMAIN | sed 's/\.com$//' | sed 's/^./\U&/')",
"Website": "https://$DOMAIN"
}" | jq .Upsert to avoid duplicates
# Upsert a Contact by Email (requires Email as an External ID or unique field)
curl -s -X PATCH "$SF_INSTANCE/services/data/v59.0/sobjects/Contact/Email/jane@acme.com" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"FirstName": "Jane",
"LastName": "Smith",
"Phone": "+1-555-0123",
"Title": "VP Engineering"
}' | jq .Scheduled sync script
Automate the export-import cycle with a shell script and cron. This script exports emails received since the last run, transforms them into Salesforce API payloads, and upserts Contacts and Tasks.
#!/usr/bin/env bash
# sync-to-salesforce.sh — Export new emails from Nylas CLI and upsert into Salesforce
set -euo pipefail
STATE_FILE="$HOME/.nylas-sf-sync-state"
LOG_FILE="$HOME/.nylas-sf-sync.log"
# Load last sync timestamp (or default to 7 days ago)
if [[ -f "$STATE_FILE" ]]; then
LAST_SYNC=$(cat "$STATE_FILE")
else
LAST_SYNC=$(date -u -d '7 days ago' '+%Y-%m-%dT%H:%M:%S' 2>/dev/null || \
date -u -v-7d '+%Y-%m-%dT%H:%M:%S')
fi
echo "[$(date -u '+%Y-%m-%d %H:%M:%S')] Starting sync since $LAST_SYNC" >> "$LOG_FILE"
# Export emails since last sync
nylas email list --json --limit 200 > /tmp/sf-emails.json
# Filter to only emails after LAST_SYNC
cat /tmp/sf-emails.json | jq --arg since "$LAST_SYNC" '
[.[] | select(.date > $since)]
' > /tmp/sf-new-emails.json
NEW_COUNT=$(cat /tmp/sf-new-emails.json | jq 'length')
echo "[$(date -u '+%Y-%m-%d %H:%M:%S')] Found $NEW_COUNT new emails" >> "$LOG_FILE"
if [[ "$NEW_COUNT" -eq 0 ]]; then
echo "No new emails since $LAST_SYNC"
exit 0
fi
# Authenticate to Salesforce
SF_RESPONSE=$(curl -s https://login.salesforce.com/services/oauth2/token \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN")
SF_TOKEN=$(echo "$SF_RESPONSE" | jq -r '.access_token')
SF_INSTANCE=$(echo "$SF_RESPONSE" | jq -r '.instance_url')
if [[ "$SF_TOKEN" == "null" || -z "$SF_TOKEN" ]]; then
echo "ERROR: Salesforce authentication failed" >> "$LOG_FILE"
exit 1
fi
# Process each unique sender — upsert as Contact
cat /tmp/sf-new-emails.json | jq -c '
[.[] | .from[0] | select(.email != null)]
| group_by(.email)
| map(.[0])
| .[]
' | while read -r sender; do
EMAIL=$(echo "$sender" | jq -r '.email')
NAME=$(echo "$sender" | jq -r '.name // ""')
FIRST=$(echo "$NAME" | awk '{print $1}')
LAST=$(echo "$NAME" | awk '{$1=""; print substr($0,2)}')
# Skip if no email
[[ -z "$EMAIL" ]] && continue
# Upsert Contact by email
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
-X PATCH "$SF_INSTANCE/services/data/v59.0/sobjects/Contact/Email/$EMAIL" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg fn "$FIRST" --arg ln "$LAST" '{
FirstName: $fn,
LastName: (if $ln == "" then "Unknown" else $ln end)
}')")
echo " Contact $EMAIL -> HTTP $HTTP_CODE" >> "$LOG_FILE"
done
# Log emails as Tasks
cat /tmp/sf-new-emails.json | jq -c '.[]' | while read -r email_obj; do
SUBJECT=$(echo "$email_obj" | jq -r '.subject // "No subject"')
DATE=$(echo "$email_obj" | jq -r '.date | split("T")[0]')
curl -s -o /dev/null \
-X POST "$SF_INSTANCE/services/data/v59.0/sobjects/Task" \
-H "Authorization: Bearer $SF_TOKEN" \
-H "Content-Type: application/json" \
-d "$(echo "$email_obj" | jq '{
Subject: .subject,
ActivityDate: (.date | split("T")[0]),
Status: "Completed",
Priority: "Normal",
Type: "Email",
Description: (.body[:500] // "")
}')"
done
# Save current timestamp for next run
date -u '+%Y-%m-%dT%H:%M:%S' > "$STATE_FILE"
echo "[$(date -u '+%Y-%m-%d %H:%M:%S')] Sync complete: $NEW_COUNT emails processed" >> "$LOG_FILE"
echo "Synced $NEW_COUNT emails to Salesforce"Schedule it with cron to run every hour:
# Run every hour — edit with: crontab -e
0 * * * * /path/to/sync-to-salesforce.sh >> /tmp/sf-sync-cron.log 2>&1Python version with simple_salesforce
For a more robust implementation, use Python with the simple_salesforce library. This script exports from Nylas CLI, creates or updates Contacts, and logs email as Tasks.
#!/usr/bin/env python3
"""Export email data from Nylas CLI and sync to Salesforce."""
import json
import os
import subprocess
import sys
from datetime import datetime
from simple_salesforce import Salesforce
# --- Salesforce connection ---
sf = Salesforce(
username=os.environ["SF_USERNAME"],
password=os.environ["SF_PASSWORD"],
security_token=os.environ["SF_SECURITY_TOKEN"],
domain="login", # use "test" for sandbox
)
print(f"Connected to Salesforce: {sf.sf_instance}")
# --- Export from Nylas CLI ---
def fetch_emails(limit: int = 200) -> list[dict]:
result = subprocess.run(
["nylas", "email", "list", "--json", "--limit", str(limit)],
capture_output=True, text=True, check=True,
)
return json.loads(result.stdout)
def fetch_contacts(limit: int = 200) -> list[dict]:
result = subprocess.run(
["nylas", "contacts", "list", "--json", "--limit", str(limit)],
capture_output=True, text=True, check=True,
)
return json.loads(result.stdout)
# --- Upsert Contacts ---
def upsert_contact(contact: dict) -> str | None:
"""Create or update a Salesforce Contact. Returns the Contact ID."""
emails = contact.get("emails", [])
if not emails:
return None
email = emails[0].get("email", "")
if not email:
return None
first_name = contact.get("given_name", "")
last_name = contact.get("surname", "") or "Unknown"
phone = ""
phone_numbers = contact.get("phone_numbers", [])
if phone_numbers:
phone = phone_numbers[0].get("number", "")
title = contact.get("job_title", "")
company = contact.get("company_name", "")
payload = {
"FirstName": first_name,
"LastName": last_name,
"Email": email,
"Phone": phone,
"Title": title,
}
# Remove empty fields
payload = {k: v for k, v in payload.items() if v}
payload["LastName"] = last_name # Required field
try:
# Try to find existing Contact by email
results = sf.query(
f"SELECT Id FROM Contact WHERE Email = '{email}' LIMIT 1"
)
if results["totalSize"] > 0:
contact_id = results["records"][0]["Id"]
sf.Contact.update(contact_id, payload)
print(f" Updated Contact: {email} ({contact_id})")
return contact_id
else:
result = sf.Contact.create(payload)
print(f" Created Contact: {email} ({result['id']})")
return result["id"]
except Exception as e:
print(f" Error upserting {email}: {e}")
return None
def upsert_contact_from_email(sender: dict) -> str | None:
"""Create or update a Contact from email sender data."""
email = sender.get("email", "")
name = sender.get("name", "")
if not email:
return None
parts = name.split(" ", 1) if name else ["", ""]
first_name = parts[0]
last_name = parts[1] if len(parts) > 1 else "Unknown"
payload = {
"FirstName": first_name,
"LastName": last_name,
"Email": email,
}
try:
results = sf.query(
f"SELECT Id FROM Contact WHERE Email = '{email}' LIMIT 1"
)
if results["totalSize"] > 0:
contact_id = results["records"][0]["Id"]
sf.Contact.update(contact_id, payload)
return contact_id
else:
result = sf.Contact.create(payload)
return result["id"]
except Exception as e:
print(f" Error upserting sender {email}: {e}")
return None
# --- Log email as Task ---
def log_email_as_task(email_msg: dict, contact_id: str | None) -> None:
"""Create a Task record for an email."""
subject = email_msg.get("subject", "No subject")
date_str = email_msg.get("date", "")
body = (email_msg.get("body", "") or "")[:500]
# Parse date to YYYY-MM-DD
activity_date = date_str.split("T")[0] if "T" in date_str else date_str
payload = {
"Subject": subject[:255],
"ActivityDate": activity_date,
"Status": "Completed",
"Priority": "Normal",
"Type": "Email",
"Description": body,
}
if contact_id:
payload["WhoId"] = contact_id
try:
result = sf.Task.create(payload)
print(f" Logged Task: {subject[:50]}... ({result['id']})")
except Exception as e:
print(f" Error logging task: {e}")
# --- Main ---
def main():
limit = int(sys.argv[1]) if len(sys.argv) > 1 else 200
# Step 1: Export and upsert contacts
print("Exporting contacts from Nylas CLI...")
contacts = fetch_contacts(limit)
print(f"Found {len(contacts)} contacts")
contact_email_to_id: dict[str, str] = {}
for contact in contacts:
emails = contact.get("emails", [])
if emails:
email = emails[0].get("email", "")
contact_id = upsert_contact(contact)
if contact_id and email:
contact_email_to_id[email] = contact_id
print(f"
Upserted {len(contact_email_to_id)} contacts to Salesforce")
# Step 2: Export emails and log as Tasks
print("
Exporting emails from Nylas CLI...")
emails = fetch_emails(limit)
print(f"Found {len(emails)} emails")
task_count = 0
for email_msg in emails:
sender = (email_msg.get("from") or [{}])[0]
sender_email = sender.get("email", "")
# Find or create the Contact for this sender
contact_id = contact_email_to_id.get(sender_email)
if not contact_id and sender_email:
contact_id = upsert_contact_from_email(sender)
if contact_id:
contact_email_to_id[sender_email] = contact_id
log_email_as_task(email_msg, contact_id)
task_count += 1
print(f"
Done: {len(contact_email_to_id)} contacts, {task_count} tasks synced to Salesforce")
if __name__ == "__main__":
main()Install the dependency and run:
pip install simple-salesforce
# Set credentials as environment variables
export SF_USERNAME="you@company.com"
export SF_PASSWORD="your-password"
export SF_SECURITY_TOKEN="your-security-token"
# Run the sync (default: 200 emails and contacts)
python3 sync_salesforce.py
# Or specify a higher limit
python3 sync_salesforce.py 500TypeScript version with jsforce
For TypeScript/Node environments, use jsforce — the most popular Salesforce client for JavaScript.
import { execFileSync } from "child_process";
import jsforce from "jsforce";
// --- Types ---
interface NylasContact {
given_name?: string;
surname?: string;
emails?: { email: string; type?: string }[];
phone_numbers?: { number: string; type?: string }[];
job_title?: string;
company_name?: string;
}
interface NylasEmail {
id: string;
from: { email: string; name: string }[];
to: { email: string; name: string }[];
subject: string;
date: string;
body?: string;
}
// --- Salesforce connection ---
const conn = new jsforce.Connection({
loginUrl: process.env.SF_LOGIN_URL || "https://login.salesforce.com",
});
async function authenticate(): Promise<void> {
const username = process.env.SF_USERNAME!;
const password = process.env.SF_PASSWORD! + (process.env.SF_SECURITY_TOKEN || "");
await conn.login(username, password);
console.log(`Connected to Salesforce: ${conn.instanceUrl}`);
}
// --- Export from Nylas CLI ---
function fetchEmails(limit = 200): NylasEmail[] {
const output = execFileSync("nylas", ["email", "list", "--json", "--limit", String(limit)], {
encoding: "utf-8",
maxBuffer: 50 * 1024 * 1024,
});
return JSON.parse(output);
}
function fetchContacts(limit = 200): NylasContact[] {
const output = execFileSync("nylas", ["contacts", "list", "--json", "--limit", String(limit)], {
encoding: "utf-8",
maxBuffer: 50 * 1024 * 1024,
});
return JSON.parse(output);
}
// --- Upsert Contacts ---
async function upsertContact(contact: NylasContact): Promise<string | null> {
const email = contact.emails?.[0]?.email;
if (!email) return null;
const payload = {
FirstName: contact.given_name || "",
LastName: contact.surname || "Unknown",
Email: email,
Phone: contact.phone_numbers?.[0]?.number || "",
Title: contact.job_title || "",
};
try {
// Search for existing Contact
const result = await conn.query<{ Id: string }>(
`SELECT Id FROM Contact WHERE Email = '${email}' LIMIT 1`
);
if (result.totalSize > 0) {
const contactId = result.records[0].Id;
await conn.sobject("Contact").update({ Id: contactId, ...payload });
console.log(` Updated Contact: ${email} (${contactId})`);
return contactId;
} else {
const created = await conn.sobject("Contact").create(payload);
if (created.success) {
console.log(` Created Contact: ${email} (${created.id})`);
return created.id;
}
return null;
}
} catch (err) {
console.error(` Error upserting ${email}: ${err}`);
return null;
}
}
async function upsertContactFromSender(
sender: { email: string; name: string }
): Promise<string | null> {
if (!sender.email) return null;
const parts = (sender.name || "").split(" ");
const firstName = parts[0] || "";
const lastName = parts.slice(1).join(" ") || "Unknown";
const payload = {
FirstName: firstName,
LastName: lastName,
Email: sender.email,
};
try {
const result = await conn.query<{ Id: string }>(
`SELECT Id FROM Contact WHERE Email = '${sender.email}' LIMIT 1`
);
if (result.totalSize > 0) {
const contactId = result.records[0].Id;
await conn.sobject("Contact").update({ Id: contactId, ...payload });
return contactId;
} else {
const created = await conn.sobject("Contact").create(payload);
return created.success ? created.id : null;
}
} catch (err) {
console.error(` Error upserting sender ${sender.email}: ${err}`);
return null;
}
}
// --- Log email as Task ---
async function logEmailAsTask(
email: NylasEmail,
contactId: string | null
): Promise<void> {
const activityDate = email.date?.split("T")[0] || "";
const description = (email.body || "").slice(0, 500);
const payload: Record<string, string> = {
Subject: (email.subject || "No subject").slice(0, 255),
ActivityDate: activityDate,
Status: "Completed",
Priority: "Normal",
Type: "Email",
Description: description,
};
if (contactId) {
payload.WhoId = contactId;
}
try {
const result = await conn.sobject("Task").create(payload);
if (result.success) {
console.log(` Logged Task: ${email.subject?.slice(0, 50)}... (${result.id})`);
}
} catch (err) {
console.error(` Error logging task: ${err}`);
}
}
// --- Main ---
async function main(): Promise<void> {
const limit = Number(process.argv[2]) || 200;
await authenticate();
// Step 1: Export and upsert contacts
console.log("Exporting contacts from Nylas CLI...");
const contacts = fetchContacts(limit);
console.log(`Found ${contacts.length} contacts`);
const emailToContactId = new Map<string, string>();
for (const contact of contacts) {
const email = contact.emails?.[0]?.email;
if (!email) continue;
const contactId = await upsertContact(contact);
if (contactId) {
emailToContactId.set(email, contactId);
}
}
console.log(`\nUpserted ${emailToContactId.size} contacts to Salesforce`);
// Step 2: Export emails and log as Tasks
console.log("\nExporting emails from Nylas CLI...");
const emails = fetchEmails(limit);
console.log(`Found ${emails.length} emails`);
let taskCount = 0;
for (const email of emails) {
const sender = email.from?.[0];
if (!sender?.email) continue;
let contactId = emailToContactId.get(sender.email) ?? null;
if (!contactId) {
contactId = await upsertContactFromSender(sender);
if (contactId) {
emailToContactId.set(sender.email, contactId);
}
}
await logEmailAsTask(email, contactId);
taskCount++;
}
console.log(
`\nDone: ${emailToContactId.size} contacts, ${taskCount} tasks synced to Salesforce`
);
}
main().catch(console.error);Install dependencies and run:
npm install jsforce
npm install -D @types/jsforce tsx
# Set credentials
export SF_USERNAME="you@company.com"
export SF_PASSWORD="your-password"
export SF_SECURITY_TOKEN="your-security-token"
# Run the sync
npx tsx sync_salesforce.ts
# Or with a custom limit
npx tsx sync_salesforce.ts 500Next steps
- Organize emails by company and domain — group your inbox by sender domain before importing to Salesforce
- Enrich contacts from email signatures — extract job titles, phone numbers, and social links to fill in Salesforce fields
- Build contact hierarchy from email — infer reporting lines and org structure for Account planning
- Map organization contacts — score relationship strength and find warm introduction paths
- CRM Email Workflows series — all 8 guides for extracting CRM intelligence from your inbox
- Full command reference — every Nylas CLI flag and subcommand documented