Skip to content
Skip to main content

Extract contacts from email threads

Last updated:

Every reply in a thread carries contact data your CRM is missing: the sender’s full name, their work address, sometimes a phone number and title buried in a signature block. Sales and support teams retype that information by hand, and half of it never makes it into the system of record. The data is already sitting in the mailbox you connected. You just need a reliable way to pull it out.

This guide shows how to read a thread, collect every participant, parse the signature on the latest message, and write a clean contact record back through one API that works the same on Gmail and Outlook.

How do I use an API to extract contact information from an email thread?

Section titled “How do I use an API to extract contact information from an email thread?”

To extract contact information from an email thread, call GET /v3/grants/{grant_id}/threads/{thread_id} to get the participants array (each entry has a name and email), then fetch the latest message body and parse the signature block for a title, company, and phone number. The thread endpoint returns this in one identical shape across providers.

A thread is the right starting point because it groups every message in the conversation and exposes a deduplicated participants list plus a message_ids array. The request below reads a single thread by ID. Use it when you want the full cast of a conversation without paging through messages one by one. A thread can hold dozens of messages, but participants collapses them into one list of distinct people.

The response gives you a subject, a participants array, and message_ids. The participants list is your first pass at who belongs in the CRM. Names here come from the email envelope, so they’re accurate but often incomplete: you’ll get “J. Rivera” where the signature says “Jordan Rivera, VP of Sales.” That’s why the next step reads the message body.

How do I parse contact details from an email signature?

Section titled “How do I parse contact details from an email signature?”

Fetch the latest message with GET /v3/grants/{grant_id}/messages/{message_id}, then run the body through a signature parser to pull a job title, company, and phone number. Nylas returns the message body verbatim, so you control the parsing. A signature usually lives in the last 6 to 10 lines, set off by a delimiter like -- or a name on its own line.

Signature parsing is heuristic, not exact. The most reliable signals are the email address (you already have it from the envelope), a phone pattern, and a title line that sits next to a known company name. Pull the message ID from the thread’s message_ids array, request that one message, and feed message.body to your parser. The body is full HTML or plain text, so strip tags before matching.

Don’t trust a parsed title or company blindly. A 2 to 5 percent error rate on free-text signatures is normal, so flag low-confidence fields for human review instead of overwriting good CRM data. For an agent-driven approach that resolves company and role from the domain, see enrich contacts from email signatures.

How do I write extracted contacts into a CRM?

Section titled “How do I write extracted contacts into a CRM?”

Send a POST /v3/grants/{grant_id}/contacts with the fields you parsed: given_name is required, and emails, company_name, job_title, phone_numbers, and notes are optional. The contact lands in the connected address book and, on Google and Microsoft, syncs back to the provider’s native contacts within a few minutes.

This is the write step that closes the loop. The request below creates one contact from parsed thread data. Use it after you’ve collected the name, address, and signature fields, and after you’ve deduplicated against existing records. Only given_name is required by the schema, so you can write a partial record and enrich it later. Adding a notes field with the thread subject gives your team context on where the contact came from.

If you mirror to an external CRM like Salesforce or HubSpot rather than the mailbox address book, treat the API as the read-and-parse layer and push the parsed object to your CRM’s own create endpoint. The sync Gmail contacts to a CRM guide covers the full two-way flow.

How do I build a contact sync feature across Gmail and Outlook?

Section titled “How do I build a contact sync feature across Gmail and Outlook?”

Build it once against one schema. The same threads, messages, and contacts endpoints return identical JSON whether the grant is Google, Microsoft, or IMAP, so a single code path covers both Gmail and Outlook. You connect each account through one OAuth flow, store the returned grant_id, and the provider differences disappear behind it.

The alternative is two integrations that never quite match. The table below shows what cross-provider contact extraction costs each way. The unified column collapses the Gmail API and Microsoft Graph into one set of calls, so adding Yahoo or iCloud later means zero new parsing code.

TaskProvider APIs directlyNylas unified API
Read a threadusers.threads.get (Gmail) and GET /me/messages (Graph)GET /v3/grants/{grant_id}/threads/{thread_id}
Get participantsParse From/To/Cc headers per providerparticipants array, same shape everywhere
Write a contactpeople.createContact and POST /me/contactsPOST /v3/grants/{grant_id}/contacts
Auth2 OAuth projects, 2 consent flows1 connector per provider, 1 grant per account
Add a 3rd providerA new integrationReuse the same code path

One honest tradeoff: if you only ever support Gmail and want raw control over Google’s People API fields, calling Google’s People API directly skips the layer. The moment a second provider appears, the unified path wins on maintenance.

How do I avoid duplicate contacts when extracting at scale?

Section titled “How do I avoid duplicate contacts when extracting at scale?”

Deduplicate on the email address before every write, because the address is the one field signature parsing can’t get wrong. List existing contacts with GET /v3/grants/{grant_id}/contacts and filter by the email query parameter, or pull contacts the API already harvested from messages by passing source=inbox. Match on a normalized, lowercased address.

The contacts list endpoint accepts a source filter that distinguishes address_book entries from inbox ones the system extracted automatically, so you can audit what already exists before adding more. At thread-extraction scale, a single busy mailbox can surface hundreds of distinct addresses, and writing each one blindly creates a duplicate storm. Check first, then create only the addresses you don’t already hold. For the read-side filtering options, see list and sync contacts.