How to Review a Listing Contract Against Brand and Compliance Rules Before Signing
Build a Spojit workflow that receives an emailed listing agreement, pulls the PDF, checks it against your agency's policy and disclosure rules with a Knowledge Query, pauses for a principal to sign off, and replies to the agent once the contract is cleared.
What This Integration Does
Listing agreements arrive by email from agents, vendors, and conveyancers, and every one of them needs to be checked against your agency's brand standards, mandatory disclosures, and compliance rules before anyone signs. Doing that by hand is slow and easy to get wrong. This Spojit workflow turns the inbox into an automated first-pass reviewer: an agent forwards the agreement to a dedicated address, Spojit reads the PDF, compares it against a maintained collection of your policy and disclosure documents, and surfaces a plain-language summary of any gaps. A principal or licensee-in-charge then approves or rejects from one place, and the agent gets a clean reply only after a human has signed off.
The workflow runs on a Mailhook trigger, so it fires within seconds of an email landing at a Spojit-generated address, with no mailbox or login to connect. The emailed PDF flows through an Attachment node into a Knowledge Query, whose findings are posted to a Slack channel for visibility and held at a Human approval node. The run pauses there until every required approver responds. On approval, a Send Email node replies to the original agent at the address they sent from. Each email is deduplicated per message, so re-sending the same agreement does not start a duplicate review, and runs leave a full execution-history entry you can audit later.
Prerequisites
- A Slack connection added under Connections -> Add connection, with a channel (for example
#listing-compliance) for the team to watch. - A persistent Knowledge collection (for example
agency-listing-policy) created in the Knowledge section of the sidebar, with your brand guidelines, mandatory disclosure templates, and compliance rules uploaded and embedded (statusREADY). - The email addresses of the principal(s) or licensee(s) who must sign off, added as approver Users in your workspace.
- Agent email domains added to the org allowlist under Settings -> General -> Email recipients so the final reply can be delivered.
- A workflow created in the Workflow Designer where you will assemble the nodes below.
Step 1: Receive the agreement with a Mailhook trigger
Open your workflow's Trigger node and set Trigger Type to Mailhook. Enter an Address prefix such as listings (1 to 24 characters), then click Generate email address. Spojit produces a unique address like listings-a1b2c3d4e5f6g7h8@mailhook.spojit.com. Copy it and share it with agents, or set up a forwarding rule from your intake inbox to it. To reduce noise, add a From allowlist of agent domains and a Subject regex such as (?i)listing|agreement. The trigger output is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, the {{ input.replyTo }} address, and an {{ input.attachments }} list of attachment references.
Step 2: Fetch the PDF with an Attachment node
Add an Attachment node directly after the trigger. This node only works with a Mailhook trigger, and it fetches the actual bytes of the attachment the trigger referenced. Set Mode to Single to grab the first match as an object, set the Content type filter to application/pdf, and set a Filename pattern of *.pdf to be safe. Turn on Fail if no attachment matches so an email with no agreement attached stops cleanly instead of moving forward with nothing. The node output gives you { filename, contentType, size, content }, where content is base64 and feeds straight into the Knowledge node. Note the 10 MB per-attachment limit; very large scanned agreements may need to be split before sending.
Step 3: Embed the contract into a transient collection
Add a Knowledge node in Embed mode so the agreement itself becomes searchable for this run. In the Collection dropdown choose Transient (auto-created per run, shared across nodes in the same run, cleaned up on completion). Set Document Type to PDF and set Document Input to the attachment bytes:
{{ attachment.content }}
Leave the embedding model at its default. Set an Output Variable such as embedded_contract so you can confirm the chunk count in the execution log. Using a transient collection here keeps each agreement isolated: you embed it, query it, and discard it, while your persistent policy collection stays clean.
Step 4: Check the contract against your policy collection with a Knowledge Query
Add a second Knowledge node in Query mode. This is the heart of the review. Set Collection to your persistent agency-listing-policy collection so the query runs against your maintained brand and compliance rules. In the Prompt field, describe exactly what to look for and reference the contract text you embedded:
Compare this listing agreement against our agency's brand and
compliance policy. The agreement text is:
{{ embedded_contract }}
Identify any clause that is missing a required disclosure, uses
non-approved brand wording, has blank or incorrect commission or
agency-term fields, or conflicts with our listing policy. List each
issue with the policy rule it breaches.
Set Result Count to 5 and pick a synthesis Model. To make the output reliable for the steps that follow, supply a Response Schema that forces structured JSON:
{
"type": "object",
"properties": {
"verdict": { "type": "string", "enum": ["clear", "issues_found"] },
"summary": { "type": "string" },
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"clause": { "type": "string" },
"rule": { "type": "string" },
"severity": { "type": "string", "enum": ["low", "medium", "high"] }
}
}
}
}
}
Set the Output Variable to review. The Knowledge node in Query mode powers this synthesis: it reads the most relevant policy chunks and returns the verdict and issue list in the shape you defined.
Step 5: Post the findings to Slack for the team
Add a Connector node on the Slack connector in Direct mode and pick the send-message tool. Map channel to your review channel (for example #listing-compliance) and build the message text from the review variables so the team sees the context at a glance:
New listing agreement for review
From: {{ input.from }}
Subject: {{ input.subject }}
Verdict: {{ review.verdict }}
{{ review.summary }}
Direct mode is deterministic and costs no AI credits, which is the right choice for a single predictable post. This keeps the channel as a living record while the principal decides, and gives anyone watching a heads-up before the approval lands in their inbox.
Step 6: Pause for a principal to sign off with a Human node
Add a Human node so a real person makes the final call. Set the Label to Listing contract approval and write a Message that includes the AI findings so the approver has everything in one place:
Approve the listing agreement from {{ input.from }}?
Verdict: {{ review.verdict }}
Summary: {{ review.summary }}
Add an Approval slot and put your principal or licensee-in-charge in it as a User atom (slots are the only required field; approval completes when every slot is satisfied). Set a Timeout (minutes) if reviews should not sit open indefinitely, and turn on Email approvers so they are notified directly. Approvers act in the Approvals inbox at /approvals. On approval the node outputs { approved: true, approvalId, outcome: "APPROVED" } and the run continues. A rejection or timeout halts the workflow, so nothing is sent back to the agent unless a human has cleared it.
Step 7: Reply to the agent with a Send Email node
After the Human node, add a Send Email node, which sends from Spojit's built-in mail service with no connection required. Set Recipients to the agent's reply address from the original email so the cleared notice goes straight back to them:
{{ input.replyTo }}
Set a templated Subject such as Re: {{ input.subject }} - cleared for signing, and write a Body that confirms the outcome. Because only upstream variables resolve here, you can reference {{ review.summary }} and the approval result but not workflow name or status. Set If sending fails to Fail the workflow so a delivery problem is visible rather than silently swallowed. The Mailhook trigger is always asynchronous and never responds to the sender on its own, so this Send Email node is how the agent learns their agreement is good to sign.
Tips
- Keep the
agency-listing-policycollection current: when a disclosure rule or brand standard changes, upload the revised document and re-embed so every future review reflects it. Use the same embedding model for embed and query. - Use the
severityfield from the Response Schema to add a Condition node before Slack: routehigh-severity verdicts to a separate urgent channel and set the Human node Urgency toHigh. - If agents sometimes send several documents in one email (agreement plus annexures), switch the Attachment node to
Multiplemode and add a Loop over{{ attachment.attachments }}to embed each one before querying. - Put the agent's domains on the email allowlist before going live, otherwise the final reply will not be delivered to external recipients.
Common Pitfalls
- Saving an Attachment node without a Mailhook trigger fails: the designer requires a Mailhook trigger for that node, so do not swap in an Email or Webhook trigger after the fact.
- Querying the transient collection instead of the persistent one returns the contract back to you instead of checking it against policy. Step 4 must point at
agency-listing-policy; only the embed step in Step 3 uses Transient. - Mismatched embedding models between your uploaded policy documents and the query will produce poor matches. Confirm the collection's documents finished embedding (status
READY) before relying on the review. - There is no "on reject do X" branch: a rejection or timeout halts the run. If you need to notify the agent of a rejection too, handle that separately rather than expecting a downstream branch after the Human node.
Testing
Before sharing the Mailhook address widely, send a single test agreement from your own address to the generated address and watch the run in execution history. Confirm the Attachment node fetched the PDF, the embed step reported a chunk count, and the Knowledge Query produced a review.verdict in the expected JSON shape. Check that the Slack message posted to your channel and that the approval appeared in the Approvals inbox. Approve it as the principal and verify the Send Email reply lands at your reply address. Once one clean pass works end to end, tighten the From allowlist and Subject regex on the trigger and roll the address out to your agents.