How to Build a Searchable Contract Archive from Emailed PDFs

Forward signed contract PDFs to a Spojit Mailhook address, fetch each attachment, embed it into a persistent Knowledge collection, and log searchable metadata to MongoDB so your team can find and query any agreement later.

What This Integration Does

Legal and operations teams receive signed contracts as email attachments from counterparties, e-signature platforms, and internal forwarding rules. Those PDFs usually end up scattered across inboxes and shared drives, impossible to search by clause or party. This workflow turns every emailed contract into two durable assets at once: a chunked, embeddable copy inside a long-lived Knowledge collection that you can ask questions of in natural language, and a structured metadata row in MongoDB that you can filter, sort, and report on. The result is a self-building contract archive that grows every time a new agreement lands, with no manual filing.

The workflow runs on a Mailhook trigger. Any email sent to your generated Mailhook address starts a run within seconds, with no mailbox or OAuth to maintain. The trigger exposes the message and its attachment references as {{ input }}. The Attachment node fetches the PDF bytes, the Knowledge node embeds them into a persistent collection named for your archive, and the MongoDB Connector node inserts a metadata document keyed by the contract filename and the sender. Because the Knowledge collection is persistent and workspace-scoped, every run adds to the same searchable archive, and any other workflow in your workspace can query it. Each message is deduplicated by the Mailhook, so the same contract forwarded twice does not create two runs.

Prerequisites

  • A MongoDB connection added under Connections → Add connection, pointing at the database where you want to store contract metadata.
  • A persistent Knowledge collection created ahead of time in the Knowledge section of the sidebar (this tutorial uses one named contracts). Note the embedding model chosen at creation; it is fixed for the life of the collection.
  • Permission to create workflows in your workspace and to generate a Mailhook address.
  • A way to route signed contract emails to your Mailhook address (a forwarding rule, an e-signature completion notification, or a shared intake address).

Step 1: Create the Mailhook Trigger

Add a Trigger node and set Trigger Type to Mailhook. Optionally set an Address prefix (1 to 24 characters, default mh) such as contracts, then click Generate email address. Spojit produces a unique address like contracts-9f3a17c2b8e0d4a1@mailhook.spojit.com. Copy it and point your contract emails at it. To keep noise out, add a From allowlist for the senders you expect (for example your e-signature platform's notification domain) and an optional Subject regex such as (?i)signed|executed|contract. The trigger output is available downstream as {{ input }}, including {{ input.from }}, {{ input.subject }}, {{ input.receivedAt }}, and {{ input.attachments }}, where each attachment reference is { id, filename, contentType }.

Step 2: Fetch the Contract PDF with the Attachment Node

Add an Attachment node directly after the trigger. The designer only allows this node when the workflow has a Mailhook trigger. Set Mode to Single to grab the first matching attachment as an object, set the Content type filter to application/pdf, and set a Filename pattern of *.pdf so only the contract document is fetched. Turn on Fail if no attachment matches so emails with no PDF do not silently move on. Name the output variable contract. The node returns:

{
  "filename": "msa-acme-2026.pdf",
  "contentType": "application/pdf",
  "size": 184320,
  "content": "JVBERi0xLjcKJ..."
}

The content field is the base64 PDF, ready to feed straight into the Knowledge node. Remember the limits: 10 MB per attachment and 25 MB per run by default, and received emails are retained for 30 days.

Step 3: Embed the Contract into a Persistent Knowledge Collection

Add a Knowledge node and set its mode to Embed. For Collection, pick your persistent collection contracts (not Transient, since this archive must outlive the run). Set File Name to the contract filename so the document is identifiable and re-uploads overwrite cleanly, for example {{ contract.filename }}. Set Document Type to PDF. For Document Input, reference the base64 bytes from the previous step: {{ contract.content }}. Leave Embedding Model blank so it uses the model fixed when the collection was created. Name the Output Variable embedded; it returns the chunk count and document metadata, for example {{ embedded.chunkCount }}. This is the step that makes the contract searchable in natural language across your workspace.

Step 4: Log Contract Metadata to MongoDB

Add a Connector node in Direct mode, choose your MongoDB connection, and select the insert-documents tool. Set collection to contracts and map documents to a single-element array describing this contract. Use upstream variables so each row is searchable later:

[
  {
    "filename": "{{ contract.filename }}",
    "subject": "{{ input.subject }}",
    "from": "{{ input.from }}",
    "receivedAt": "{{ input.receivedAt }}",
    "sizeBytes": {{ contract.size }},
    "chunkCount": {{ embedded.chunkCount }},
    "collection": "contracts",
    "status": "ARCHIVED"
  }
]

The tool returns insertedCount and the generated document IDs. This MongoDB row is your index: you can later run find-documents on the contracts collection with a filter such as { "from": "legal@acme.com" } to list every agreement from a counterparty, while the Knowledge collection answers the deeper "what does this contract say" questions.

Step 5: Confirm Receipt to the Sender

Mailhook runs are always asynchronous, so the sender gets no automatic reply. Add a Send Email node to acknowledge intake. Set Recipients to {{ input.replyTo }}, give it a Subject like Contract received and archived: {{ contract.filename }}, and a short Body confirming the document was filed. Set If sending fails to Continue anyway so a bounce never loses the archived contract. External recipients must be on your org allowlist under Settings → General → Email recipients, and these messages count toward your monthly email allowance.

Step 6: Query the Archive On Demand

To prove the archive is searchable, build (or reuse) a second workflow that reads from the same collection. Add a Knowledge node in Query mode, set Collection to contracts, and supply a natural language Prompt such as What is the termination notice period in the Acme master services agreement?. Set Result Count (default 5) and optionally a Response Schema to force structured JSON. Because Knowledge collections are workspace-scoped, this query workflow can run on a Manual or Webhook trigger and still read everything Step 3 has embedded. For broader retrieval patterns over this kind of collection, see the related article below on using RAG to answer questions from company documents.

Tips

  • Use the contract filename as both the Knowledge File Name and the MongoDB filename field so the two stores stay joinable. A re-sent contract overwrites its Knowledge document in place instead of duplicating it.
  • If counterparties sometimes send more than one PDF per email, switch the Attachment node to Multiple mode and wrap Steps 3 and 4 in a Loop node over {{ contract.attachments }}, embedding and logging each attachment in turn.
  • Keep the same embedding model for the whole life of the collection. Embed and query must use the same model, which is why the collection's model is fixed at creation.
  • Ask Miraxa, the intelligent layer across your automation, to scaffold this on the canvas with a prompt like "Build a workflow that watches a mailhook, fetches the PDF attachment, embeds it into the contracts collection, and inserts a metadata row into MongoDB," then fine-tune each node in the properties panel.

Common Pitfalls

  • Choosing Transient instead of your persistent collection in Step 3. A transient collection is auto-cleaned when the run finishes, so the contract would vanish from the archive. Always select the named contracts collection here.
  • Forgetting to turn on Fail if no attachment matches. Without it, a notification email with no PDF passes through and you log an empty metadata row. With it on, those runs fail loudly so you can adjust your From allowlist or Subject regex.
  • Oversized files. The Attachment node defaults to 10 MB per attachment and 25 MB per run. Large scanned contracts can exceed this, so confirm your source exports reasonably sized PDFs.
  • Regenerating the Mailhook address. Regenerate address kills the old address instantly, so any forwarding rule still pointing at it stops delivering. Update your routing whenever you rotate.

Testing

Before pointing production forwarding rules at the Mailhook, send one sample signed PDF from an allowlisted address to your generated address. Open the run in execution history and confirm the Attachment node produced a contract object with a non-zero size, the Knowledge node reported a chunk count in embedded.chunkCount, and the MongoDB step returned insertedCount: 1. Then open the contracts collection in the Knowledge section and verify the document shows status READY, and run a quick Query-mode prompt against it. Once one contract round-trips cleanly into both stores, enable the workflow and connect your real forwarding rules.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.