How to Process a Signed Contract Email into a Searchable Knowledge Library with Slack Q and A
Receive a signed contract by email, embed every attached document into a persistent knowledge library, post a Slack summary, and let your team ask about renewal dates and clauses with cited answers.
What This Integration Does
Professional services teams sign a steady stream of client contracts, statements of work, and amendments, and each one arrives as one or more PDFs attached to an email. Filing them by hand and remembering what is in them is slow, and weeks later nobody can recall when a retainer renews or what the termination clause says. This Spojit workflow turns the moment a signed contract lands in your inbox into a durable, searchable client-contracts library. When the documents are filed, the workflow asks the library plain questions like "what is the renewal date and notice period" and posts the cited answer straight into a Slack channel so account managers see it without opening a single file.
The workflow runs whenever a contract email is sent to a dedicated Spojit mailhook address. A Mailhook trigger fires within seconds of delivery, an Attachment node in Multiple mode collects the bytes of every attached document, and a Loop node embeds each one into a persistent client-contracts collection so the library grows with every contract you receive. A Knowledge node in Query mode then asks the library for the key terms, and a Connector node on the slack connector posts the summary. Because the collection is persistent, every future run adds to the same searchable archive, and re-running the same email is safe: messages are deduplicated per message and embedding a file with a name that already exists simply overwrites that document rather than duplicating it.
Prerequisites
- A slack connection (OAuth) with permission to post to the channel you want, plus the channel ID (for example
C0123ABCD) where summaries should land. - A persistent knowledge collection named
client-contracts. Create it in the Knowledge section of the sidebar with New Collection; the embedding model is fixed at creation, so leave the default (Gemini Embedding 001) selected. - An agreed place to send signed contracts. You will generate a Spojit mailhook address in Step 1 and either forward contracts to it or point your signing tool's completion notification at it.
- Optional: a monday connection if you also want to log each contract as an item on a board (Step 6).
- The signed contracts you send should be standard documents (PDF or Word). Keep each attachment under 10 MB and each email under 25 MB total, which are the default mailhook attachment limits.
Step 1: Start the workflow with a Mailhook trigger
Create a new workflow and open the Trigger node. Set Trigger Type to Mailhook. Enter an Address prefix such as contracts (1 to 24 characters), then click Generate email address. Spojit produces a unique address in the form contracts-<random16>@mailhook.spojit.com. Copy it and either forward signed contracts there or set it as the recipient of your signing tool's "completed" notification. No mailbox or OAuth is required, and a run starts within seconds of any mail arriving.
To keep noise out, add filters: a From allowlist limited to your signing provider or your own domain, and an optional Subject regex such as (?i)signed|executed|completed. The trigger fires whether the address is in To, Cc, or Bcc, and identical messages are deduplicated. The email is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and the attachment references in {{ input.attachments }}.
Step 2: Collect every attachment with an Attachment node in Multiple mode
Add an Attachment node directly after the trigger. This node fetches the actual bytes of the files referenced by the mailhook and only works in a mailhook workflow. Configure it like this:
- Mode:
Multipleso it returns a list of every matching attachment, not just the first. - Content type:
application/pdf, application/vnd.openxmlformats-officedocument.wordprocessingml.documentto accept PDFs and Word documents (or simplyapplication/pdfif contracts are always PDFs). - Filename pattern: optional glob such as
*.pdfif you want to skip everything else. - Fail if no attachment matches: turn this
onso a contract email with no usable document stops loudly rather than filing nothing.
Give it an output variable such as files. In Multiple mode the output is shaped like this, where each item's content is base64 you can feed straight into a Knowledge node:
{
"attachments": [
{ "filename": "MSA-signed.pdf", "contentType": "application/pdf", "size": 184213, "content": "JVBERi0xLjci..." },
{ "filename": "SOW-1-signed.pdf", "contentType": "application/pdf", "size": 90122, "content": "JVBERi0xLjci..." }
],
"count": 2,
"totalBytes": 274335
}
Step 3: Loop over each document and embed it into the persistent collection
Add a Loop node set to ForEach, iterating over {{ files.attachments }}. Inside the loop body, add a Knowledge node in Embed mode and configure it against your long-lived library:
- Collection:
client-contracts(the persistent collection you created in Prerequisites). - File Name:
{{ attachment.filename }}so each document keeps its real name. If the same name arrives again, the existing document is overwritten rather than duplicated, which keeps the library clean across re-runs. - Document Type:
PDF(orWordfor .docx). If you accept both formats, branch on{{ attachment.contentType }}with a Condition node, or keep the Attachment filter to PDF only and run a separate Word path. - Document Input:
{{ attachment.content }}, the base64 bytes from the loop item. - Output Variable: for example
embedResult, which returns the chunk count and document metadata.
Use the same embedding model for embed and query of a collection. Since the collection's model was fixed at creation, leave Embedding Model at its default here so it matches what your queries will use later.
Step 4: Ask the library for renewal dates and key clauses with a Knowledge node in Query mode
After the loop completes, add a second Knowledge node, this time in Query mode, pointed at the same client-contracts collection. This searches the whole library, so it can reason across the documents you just filed (and any filed by earlier runs). Configure:
- Collection:
client-contracts. - Prompt: write a focused question scoped to this contract, for example:
For the contract titled "{{ input.subject }}", summarise the parties,
the effective date, the renewal or expiry date, the notice period
required to terminate, and any auto-renewal clause. Quote the exact
clause wording where possible and name the source document.
- Result Count: leave at the default
5, or raise it if contracts are long and you want more passages retrieved. - Model: pick the AI model that synthesises the answer (this is where Miraxa, the intelligent layer across your automation, reads the retrieved passages and writes the answer).
- Response Schema: optionally force structured JSON so the next steps can use individual fields reliably, for example:
{
"type": "object",
"properties": {
"parties": { "type": "string" },
"effectiveDate": { "type": "string" },
"renewalDate": { "type": "string" },
"noticePeriod": { "type": "string" },
"autoRenews": { "type": "boolean" },
"summary": { "type": "string" },
"sourceDocuments": { "type": "array", "items": { "type": "string" } }
}
}
Set the Output Variable to contractAnswer. Because the prompt asks the node to name the source document, the answer carries citations your team can trust.
Step 5: Post the cited answer to Slack
Add a Connector node on the slack connector in Direct mode and pick the send-message tool. Map its inputs:
- channel: your target channel ID, for example
C0123ABCD. - text: a templated summary built from the query result. If you used the Response Schema above:
New signed contract filed: {{ input.subject }}
Parties: {{ contractAnswer.parties }}
Effective: {{ contractAnswer.effectiveDate }}
Renews/expires: {{ contractAnswer.renewalDate }}
Notice required: {{ contractAnswer.noticePeriod }}
Auto-renews: {{ contractAnswer.autoRenews }}
Summary: {{ contractAnswer.summary }}
Sources: {{ contractAnswer.sourceDocuments }}
Documents filed this run: {{ files.count }}
If you skipped the schema, reference the whole answer with {{ contractAnswer }} instead. Direct mode is deterministic and spends no AI credits, which is the right choice for a single predictable post. If you would rather have the message routed to a channel chosen by reasoning over the contract, switch the node to Agent mode and describe the rule in the prompt instead.
Step 6: Confirm receipt and optionally log the contract
Add a Send Email node to acknowledge the sender. Set Recipients to {{ input.replyTo }} (mailhook runs are always asynchronous, so this Send Email node is how you reply), give it a Subject like Contract received and filed: {{ input.subject }}, and a short Body confirming the documents are now searchable. External recipients must be on your org allowlist under Settings -> General -> Email recipients.
If you track contracts on a board, add another Connector node on the monday connector in Direct mode using create-item, mapping the item name to {{ input.subject }} and column values such as the renewal date to {{ contractAnswer.renewalDate }}. This gives account managers a board view alongside the searchable library. For the standalone version of that pattern, see the related Monday.com tutorial linked below.
Tips
- Let Miraxa, the intelligent layer across your automation, scaffold the skeleton: open the chat in the designer and ask "Build a workflow that watches a mailhook, fetches every PDF attachment, embeds each into the
client-contractscollection, queries it for renewal terms, and posts the result to Slack." Then fine-tune each node in the properties panel. - Keep the embed and query embedding models identical. The collection's model is fixed at creation, so leaving both Knowledge nodes on the collection default avoids mismatched results.
- For one-off "embed and discard" extraction (a single quote you do not want to keep), pick Transient as the collection instead of
client-contracts: it is auto-created per run, shared across nodes in that run, and cleaned up on completion. This tutorial uses a persistent collection precisely because you want the archive to last. - You can run a separate, query-only workflow against
client-contractslater so anyone can ask "which contracts renew next quarter" without re-sending an email, since collections are workspace-scoped and readable from any workflow.
Common Pitfalls
- The designer refuses to save an Attachment node unless the trigger is a Mailhook. If you started from an Email trigger, the node will not save; switch the trigger to Mailhook first.
- Forgetting Multiple mode means only the first document is filed. With several attachments per contract email, set Mode to
Multipleand loop over{{ files.attachments }}. - Attachment and run size limits apply (10 MB per attachment, 25 MB per run by default). A scanned contract bundle can exceed these; split very large bundles or send documents across separate emails.
- Re-sending the same email is deduplicated per message, but if you genuinely re-file a revised contract under the same file name it overwrites the prior document. Use distinct file names (for example include a version) when you want both kept.
- Set Fail if no attachment matches to on. Otherwise a contract email with the PDF inline or in an unsupported type files nothing silently and the Slack post will be empty.
Testing
Before pointing your signing tool at the address, send yourself a single test email to the generated mailhook address with two small PDFs attached. Open Execution Logs and confirm the Attachment node reports count: 2, the Loop runs twice, and each Embed step returns a chunk count. Open the client-contracts collection in the Knowledge section and verify both documents appear with status READY. Then check the Slack channel for the summary and confirm the cited source document names match the files you sent. Once the answer reads correctly, widen the From allowlist and route real contracts in.
Learn More
- Mailhook trigger reference
- Attachment node reference
- Knowledge node and collections
- Slack connector reference
- How to Create Shopify Orders from PO PDFs Emailed to a Mailhook
- How to Build a Searchable Knowledge Base from PDF Documents
- How to Use RAG to Answer Questions from Company Documents
- Using Knowledge Nodes
- Using Loop Nodes
- How to Create Monday.com Tasks from Emails