Captain indexes an email as one text document for the message and one document per attachment, linked by relations and sharing the same metadata. This page covers what one email becomes, how the parts are linked, the metadata keys, identity and re-indexing, and examples.
This is useful for
- Supplier and customer correspondence, where the answer is in the attachment rather than the message
- Threads that need to be searched as a whole, from the first message to the last reply
- Mailboxes exported from Outlook or an archive, where attachments must keep the sender and date they arrived with
- Collections where deleting or re-sending an email should update its parts in place
What one email becomes
Send a .msg or .eml through any indexing route (upload, raw bytes, or a cloud storage
connector). Captain produces:
- One text document for the message:
From,To,Cc,DateandSubject, then the body, with HTML rendered to text. - One document per attachment, parsed by its own lane and billed as its type.
- A forwarded message attached to the email is a body of its own, linked to the outer message. Forwards nested deeper than one level are skipped.
Not indexed: inline signature images, empty attachments, a second copy of identical bytes within the same email, and attachment types Captain does not support.
How an email is chunked
Each part uses its own lane
A PDF attachment is chunked as a document, with page ranges. A workbook is chunked as a sheet, with row bands. An image is chunked as an image. The body is a short text document.
Relations
email_attachment links the body to each attachment. email_reply_to links a reply to
the message it answers, taken from In-Reply-To and then the last References id. Both
are chunk relations with one row each, readable from either end with
relation_direction: "both". A reply whose parent arrives in a later job gets no
relation but shares email_thread_id.
Metadata on every part
All parts carry email_role, email_message_id, email_thread_id, email_from,
email_subject, email_date (ISO 8601 UTC) and email_source_uri. The body also
carries email_in_reply_to and email_attachment_count. Attachments also carry
email_parent_identity, email_attachment_name and email_content_sha256. A header
the email did not have is stored as an empty string rather than omitted, so a filter on
it does not silently exclude a part. The job’s custom_metadata is copied onto every
part. Text values are cut at 1,024 characters.
Identity and re-indexing
The body’s identity is msg://<Message-ID>. An attachment’s is
msg://<Message-ID>#att/<file name>, with ~2 for a second attachment of the same
name. Neither depends on the file’s location or the job, so re-indexing an email
replaces its parts in place. An attachment that is no longer in the email is removed,
with its relation rows. Deleting the email from a synced bucket, or deleting the body by
document id, removes every attachment. A message with no Message-ID uses its file
location as identity.
Examples
Find a quote by what the email said about it
Goal: return the attached quote together with the message that sent it, searching by the message text.
The body matched the query. The email_attachment relation adds the PDF’s passage to
the result, and the attachment carries the sender and date of the email.
Search one thread
Goal: limit a search to one reply chain.
Every message and attachment in the thread shares email_thread_id.
List one email's attachments
Goal: return only the attachments of one message.
Every attachment carries its body’s identity in email_parent_identity.
Everything from one sender in a month
Goal: return messages and attachments from one sender within a date range.
Attachments carry email_from and email_date as well as the body, so the filter
returns both. email_date is an ISO 8601 string, so the range comparison is correct.
See Supported File Types for the extension table and Advanced Search & Relations for relation traversal.