Raw email is one of the worst data formats you’ll ever have to read. A single message can carry a nested MIME tree, base64-encoded parts, quoted-printable bodies, mismatched character sets, and both an HTML and a plain-text copy of the same content. Parsing that by hand with Python’s email module or a tangle of IMAP FETCH commands works until it doesn’t, and the day it breaks is usually the day a customer sends mail from a client you never tested.
This guide shows how to retrieve a message through an API and get back a clean, already-parsed body plus the fields you actually care about: sender, recipients, subject, date, and attachments. No MIME tree walking, no charset guessing, no IMAP socket handling. The examples are Python-forward, with a curl version for each call.
How do I parse an email body in Python using an API?
Section titled “How do I parse an email body in Python using an API?”Send GET /v3/grants/{grant_id}/messages/{message_id} and read the body, subject, from, and to fields off the JSON response. The API parses the MIME structure for you and returns the body as a single string: HTML when the message has it, plain text otherwise. You skip Python’s email.message_from_bytes and the multipart walking it forces.
The call below fetches one message by ID and prints the parsed pieces. The body field holds the decoded content, snippet gives you the first 100 characters with HTML stripped (handy for previews), and from is a list of name/email pairs because a message can technically have more than one sender. One request returns all of it.
from nylas import Client
nylas = Client(api_key="<NYLAS_API_KEY>")
message = nylas.messages.find( identifier="<NYLAS_GRANT_ID>", message_id="<MESSAGE_ID>",).data
print("Subject:", message.subject)print("From:", message.from_[0]["email"])print("Preview:", message.snippet)print("Body:", message.body)curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/<MESSAGE_ID>' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'How do I read incoming email using an IMAP API?
Section titled “How do I read incoming email using an IMAP API?”Connect an IMAP mailbox once as a grant, then call GET /v3/grants/{grant_id}/messages to read incoming mail through the same parsed JSON shape Gmail and Outlook return. You never open an IMAP socket, send FETCH commands, or decode MIME parts yourself. The unified layer speaks IMAP for you and hands back the body already extracted.
The list call below pulls the most recent messages, newest first, and works identically across Google, Microsoft, Yahoo, iCloud, and generic IMAP accounts. Set limit up to 200 per page; the default is 50. Reading each message’s body here is the same field you’d read from the single-message call, so one parser handles every provider. That cross-provider consistency is the biggest reason to go through an API instead of a raw IMAP client.
from nylas import Client
nylas = Client(api_key="<NYLAS_API_KEY>")
messages = nylas.messages.list( identifier="<NYLAS_GRANT_ID>", query_params={"limit": 50},).data
for message in messages: print(message.subject, "->", message.snippet)curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?limit=50' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'How can I extract structured data from incoming email?
Section titled “How can I extract structured data from incoming email?”Fetch the message, then strip the noise before you read it. The clean messages call, PUT /v3/grants/{grant_id}/messages/clean, removes signatures, quoted reply history, and tracking pixels, leaving the meaningful body. Feed that tidy text to a parser or a language model, and your field extraction gets noticeably more accurate because there’s no boilerplate to confuse it.
The request below cleans messages in one call, since message_id is an array. Set html_as_markdown to true when the output is headed for an LLM, because markdown keeps headings and lists that plain text drops (this beta flag requires images_as_markdown to be true). Add remove_conclusion_phrases to strip closing lines such as “Best” and “Regards”, and ignore_tables to drop <table> markup while keeping the row text. For order confirmations, receipts, and lead forms, cleaning first removes the parts that have nothing to do with the data you want, which is why field extraction lands more reliably on the cleaned body than on the raw one.
import requests
response = requests.put( "https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/clean", headers={"Authorization": "Bearer <NYLAS_API_KEY>"}, json={ "message_id": ["<MESSAGE_ID>"], "ignore_links": True, "ignore_images": True, "remove_conclusion_phrases": True, },)
cleaned = response.json()["data"][0]["conversation"]print(cleaned)curl --request PUT \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages/clean' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer <NYLAS_API_KEY>' \ --data '{ "message_id": ["<MESSAGE_ID>"], "ignore_links": true, "ignore_images": true, "remove_conclusion_phrases": true }'API parsing vs writing your own MIME parser
Section titled “API parsing vs writing your own MIME parser”Python’s standard library can parse email, and for a single trusted source it’s fine. The trouble starts at scale: you own the IMAP connection pool, the charset fallbacks, the multipart edge cases, and the per-provider auth. The table compares the two approaches on the work that actually fills your sprint, with the unified API column first.
| Task | Unified API | Your own MIME parser |
|---|---|---|
| Get the body | Read the body field | Walk the MIME tree, pick HTML vs text |
| Decode parts | Done for you | Handle base64, quoted-printable, charsets |
| Strip quotes/signatures | messages/clean call | Write and maintain regex heuristics |
| Connect a mailbox | One grant, any provider | Per-provider IMAP/OAuth wiring |
| Get attachments | attachments array on the message | Parse and decode each MIME part |
The honest tradeoff: if you only ever read one internal IMAP mailbox and want zero external dependencies, Python’s built-in email package is a perfectly good answer, and it ships with the language. The API earns its place once you support multiple providers, need clean body text, or don’t want to babysit MIME parsing in production. Below roughly 100 messages a day from a single trusted sender, rolling your own is reasonable.
What fields can I extract from a parsed message?
Section titled “What fields can I extract from a parsed message?”A parsed message returns everything you’d otherwise reconstruct from MIME headers and parts. Beyond body and snippet, you get from, to, cc, and bcc as name/email pairs, subject, date as a Unix timestamp, thread_id for grouping replies, and an attachments array with each file’s content_type, filename, and size. The unread and starred booleans round out the common metadata.
When you need the raw envelope, the fields query parameter accepts include_headers to return the full header array, or raw_mime to get the Base64url-encoded original message and decode it yourself. To trim the response and cut latency, pass select with a comma-separated list, for example select=id,subject,from,date, so you transfer only the 4 fields you read. That’s a real bandwidth win when you list hundreds of messages and only render a subject line and sender, and it pairs well with limit to keep each page small.
curl --request GET \ --url 'https://api.us.nylas.com/v3/grants/<NYLAS_GRANT_ID>/messages?select=id,subject,from,date&limit=50' \ --header 'Authorization: Bearer <NYLAS_API_KEY>'What’s next
Section titled “What’s next”- Clean message HTML and quoted text to strip signatures and replies before parsing
- Extract structured data from email to turn a cleaned body into JSON with a language model
- List Google email messages for filtering and pagination on Gmail accounts
- Getting started with Nylas to create a project, connector, and your first grant