How to Index Mailhook Contracts into a Knowledge Base for Slack Q&A

Forward contract PDFs to a Spojit Mailhook address, fetch the attachment bytes, embed them into a persistent Knowledge collection, and post a Slack confirmation so your team can ask questions about the contracts later.

What This Integration Does

Legal, sales, and operations teams sign new contracts constantly, and those signed PDFs usually arrive by email from a counterparty, an e-signature provider, or an internal forwarding rule. Instead of someone manually saving each file into a shared drive, this workflow turns a dedicated email address into an ingestion pipeline: every contract that lands becomes a searchable document in a long-lived Knowledge collection. Once a contract is indexed, anyone can query the collection from another Spojit workflow to answer questions like "what is the renewal date on the Acme MSA?" without opening the file.

The workflow is triggered by a Mailhook trigger, so a run starts within seconds of an email arriving at your unique Spojit address. The Attachment node pulls the raw PDF bytes, the Knowledge node embeds those bytes into a persistent collection that survives across runs, and a final Slack message confirms the indexing. Because the collection is persistent and workspace-scoped, every contract you send accumulates in the same archive, and re-running the workflow on the same file simply overwrites that one document rather than creating duplicates.

Prerequisites

  • A Slack connection added under Connections -> Add connection -> Slack, with permission to post to your target channel.
  • The Slack channel ID (or name) where confirmations should be posted, for example #contracts-intake.
  • A persistent Knowledge collection created ahead of time in the Knowledge section of the sidebar (see Step 1). Note its embedding model so you can stay consistent later.
  • A way to route contract emails to your Mailhook address: a forwarding rule from a shared mailbox, your e-signature provider's notification settings, or a counterparty you tell to use the address.
  • Contracts delivered as PDF attachments under 10 MB each (the per-attachment default limit).

Step 1: Create the persistent Knowledge collection

Open the Knowledge section in the sidebar and choose New Collection. Give it a clear name such as signed-contracts and an optional description. The embedding model is fixed at creation time, so pick Gemini Embedding 001 (the default) and keep that choice in mind: every embed and every later query against this collection must use the same model. This is a persistent collection, meaning the documents stay available across runs and can be read by any workflow in the workspace. You only do this once; the workflow itself reuses the collection on every run.

Step 2: Set up the Mailhook trigger

Create a new workflow and add a Trigger node. Set Trigger Type to Mailhook. Optionally set an Address prefix such as contracts (1 to 24 characters), then click Generate email address. Spojit produces a unique address of the form contracts-<random16>@mailhook.spojit.com. Copy it and point your contract emails at it through a forwarding rule or your e-signature provider's notifications. To keep noise out, add an optional From allowlist (for example your e-signature domain) and a Subject regex like (?i)signed|executed|contract. The Mailhook fires whether the address is in To, Cc, or Bcc, and the trigger output is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and the {{ input.attachments }} references.

Step 3: Fetch the PDF with an Attachment node

Add an Attachment node. This node only works with a Mailhook trigger, and the designer will not save it without one. Set Mode to Single so you get the first matching file as a single object. Restrict it to PDFs with Content type set to application/pdf and a Filename pattern of *.pdf. Turn on Fail if no attachment matches so a contract email that arrives without a PDF surfaces as a failed run instead of silently passing through. The node fetches the actual bytes and exposes an output you can reference as:

{
  "filename": "acme-msa-signed.pdf",
  "contentType": "application/pdf",
  "size": 184213,
  "content": "JVBERi0xLjcKJ..."   // base64
}

The content field is base64 and feeds straight into the Knowledge node in the next step.

Step 4: Embed the contract into the collection

Add a Knowledge node and set its mode to Embed. Configure the fields:

  • Collection: select the persistent signed-contracts collection you created in Step 1 (not Transient, since you want these documents to last).
  • Document Type: PDF.
  • Document Input: reference the attachment bytes with {{ attachment.content }}.
  • File Name: give each document a stable, human-readable name so it is identifiable in the document table and so re-sends overwrite rather than duplicate. Use the email subject, for example {{ input.subject }}.pdf, or the original filename {{ attachment.filename }}.
  • Output Variable: name it something like embedResult. It returns the chunk count and metadata once embedding completes.

If a document with the same File Name already exists in the collection, embedding overwrites it, which keeps your archive clean when the same contract is forwarded twice.

Step 5: Post a Slack confirmation

Add a Connector node, choose the Slack connector, and use Direct mode so the call is deterministic and costs no AI credits. Select the send-message tool. Map its inputs to your channel and a templated message that pulls from upstream variables:

channel: "#contracts-intake"
text: "New contract indexed: {{ attachment.filename }}
From: {{ input.from }}
Subject: {{ input.subject }}
Chunks embedded: {{ embedResult.chunkCount }}
Ask Miraxa or query the signed-contracts collection to look it up."

This posts a single confirmation per indexed contract so the team can see exactly what arrived and was added to the knowledge base. If you prefer the message to land inside an existing thread or a different channel per sender, you can branch with a Condition node before this step.

Step 6: Query the collection for Slack Q&A

Indexing is only half the value: the point is to answer questions later. In a separate workflow (for example one triggered by a Slack slash command routed through a Webhook trigger, or a Manual trigger you run on demand), add a Knowledge node in Query mode. Point its Collection at the same persistent signed-contracts collection, supply a natural-language Prompt such as {{ input.question }}, set a Model for synthesis, and keep Result Count at the default of 5. Capture the answer in an Output Variable and post it back to Slack with another Slack send-message call. Because the embedding model on the collection is fixed, this query workflow must rely on the same model you chose in Step 1.

Tips

  • Keep the File Name deterministic (subject or original filename) so resending the same contract overwrites one document instead of piling up near-duplicates that dilute query results.
  • Use the Subject regex and From allowlist on the Mailhook to filter out reply chains and unrelated mail before they reach the Attachment node.
  • For Q&A accuracy, ask scoped questions ("renewal date on the Acme MSA") rather than broad ones, and raise Result Count if answers seem to miss context from longer contracts.
  • Always use Direct mode on the Slack node here; the action is a single predictable send, so there is no reason to spend AI credits on Agent mode.

Common Pitfalls

  • Mixing embedding models. The collection's embedding model is fixed at creation. If a later query workflow uses a different model, retrieval quality collapses. Keep embed and query on the same model.
  • Transient vs persistent. If you pick Transient in the Embed node's Collection dropdown, the document is discarded when the run ends and nothing accumulates. For a long-lived contract archive you must select the persistent collection.
  • Size limits. Attachments default to a 10 MB per-file and 25 MB per-run cap, and received emails are retained for 30 days. Large scanned contracts may exceed the limit; compress or split before forwarding.
  • Missing attachments. A "contract" email with the PDF as an inline link rather than an attachment yields nothing for the Attachment node. Turning on Fail if no attachment matches makes that obvious instead of silently posting an empty Slack confirmation.

Testing

Before pointing real contract traffic at the address, send a single small sample PDF from an allowlisted sender to your Mailhook address and watch the run in execution history. Confirm the Attachment node returned a non-zero size, the Knowledge embed step reported a chunk count in {{ embedResult.chunkCount }}, and the Slack confirmation arrived in the right channel. Then open the signed-contracts collection in the Knowledge section and verify the document shows status READY with the expected file name. Finally, run your Query workflow with a question you know the answer to so you can validate retrieval end to end before enabling the workflow for production mail.

Learn More

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