How to Answer Inbound RFP Questions Automatically with AI and a Knowledge Base
Build a Spojit workflow that catches an RFP document emailed to a Mailhook address, embeds it into a transient collection, drafts answers against your company-capabilities knowledge base, waits for a human to approve, then replies from your own domain with Resend.
What This Integration Does
Responding to a Request for Proposal (RFP) usually means a sales or bid team manually reads a vendor questionnaire, hunts through old proposals and capability statements for the right answers, and pastes them back into a reply. This workflow does the reading and first-draft answering for you. When a prospect emails their RFP document to a dedicated Spojit Mailhook address, the workflow fetches the attached file, embeds it so it can be searched, and uses a Knowledge node in Query mode to draft answers grounded in a persistent collection of your real company capabilities. A teammate reviews the draft before anything goes out, so a person always signs off on what the prospect receives.
The run is push-based: any mail to the Mailhook address starts an execution within seconds, with no mailbox or polling involved. The RFP file flows from the Attachment node into a Knowledge embed step that writes to a transient collection scoped to that single run, then a second Knowledge query step reads both the transient RFP text and your long-lived capabilities collection to produce a draft. A Human node pauses the workflow until an approver accepts the draft; on approval the Resend connector sends the reply. Each email is deduplicated, so a forwarded or re-sent message will not double-run, and the transient collection is discarded automatically when the run finishes, leaving no per-RFP cleanup for you.
Prerequisites
- A Resend connection added under Connections, with a verified sending domain so replies come from your own address.
- An optional Slack connection if you want to notify a deals channel while the approval is pending.
- A persistent Knowledge collection (for example
company-capabilities) already created and populated with your capability statements, past winning answers, security and compliance docs, and standard boilerplate. Note which embedding model it uses. - Permission to create workflows, plus at least one User, Role, or Team you can assign as an approval slot in the Approvals inbox.
- A place to point RFP mail: a forwarding rule, a shared inbox auto-forward, or a published intake address you control.
Step 1: Trigger on the inbound RFP email with a Mailhook
Add a Trigger node and set its type to Mailhook. Optionally set an Address prefix such as rfp (1 to 24 characters), then choose Generate email address. Spojit creates a unique address like rfp-a1b2c3d4e5f6g7h8@mailhook.spojit.com. Copy it and point your RFP intake at it (a forwarding rule on a shared bids mailbox works well). To keep noise out, add an optional From allowlist or a Subject regex such as (?i)rfp|proposal|questionnaire.
The trigger output is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and an {{ input.attachments }} array where each entry is a reference of the form { id, filename, contentType }. The Mailhook never replies to the sender on its own, so you will send the answer yourself in a later step.
Step 2: Fetch the RFP document with an Attachment node
Add an Attachment node directly after the trigger. This node only saves on a Mailhook workflow, and it pulls the actual bytes of the file referenced by the trigger. Set Mode to Single so you get one file as an object. Restrict it to the document you expect with a Content type filter such as application/pdf and a Filename pattern like *.pdf. Turn on Fail if no attachment matches so a body-only email does not silently run the AI steps with nothing to read.
In Single mode the output is { filename, contentType, size, content }, where content is the base64-encoded file. Call the output variable rfp_file so you can reference {{ rfp_file.content }} next. Keep in mind the default limits of 10 MB per attachment and 25 MB per run.
Step 3: Embed the RFP into a transient collection
Add a Knowledge node in Embed mode. In the Collection dropdown choose Transient, which auto-creates a per-run collection that is shared across nodes in this same execution and cleaned up automatically when the run completes. A transient collection needs no file name or embedding model. Set Document Type to PDF and set Document Input to {{ rfp_file.content }}, the base64 from the Attachment node. Name the Output Variable rfp_embedded so you can confirm a chunk count before querying.
Embedding the RFP, rather than pasting its raw text into a prompt, lets the next step pull only the relevant questions even when the document is long. Because the collection is transient, the RFP content is never retained past this run.
Step 4: Draft answers against your capabilities knowledge base
Add a second Knowledge node in Query mode. To pull from your long-lived answers, set its Collection to your persistent company-capabilities collection. In the Prompt, instruct the agent to read the RFP questions and answer each one only from the retrieved company material. Set a Result Count high enough to cover several questions (for example 8), pick a capable Model for synthesis, and use a Response Schema so the draft comes back as structured JSON you can format reliably. A workable prompt:
You are drafting answers to an inbound RFP for our company.
The RFP questions are in the transient collection embedded earlier this run.
Answer each question using ONLY the retrieved company-capabilities content.
If a question cannot be answered from that content, mark it "needs human input".
Do not invent certifications, numbers, or claims.
If you prefer to keep the questions and the answers in one query, you can instead run two queries: one against Transient to list the questions, then this one against company-capabilities with those questions pasted into the prompt. Set the Output Variable to draft. A Response Schema such as the following keeps the output predictable:
{
"type": "object",
"properties": {
"answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"question": { "type": "string" },
"answer": { "type": "string" },
"needs_human_input": { "type": "boolean" }
},
"required": ["question", "answer", "needs_human_input"]
}
}
},
"required": ["answers"]
}
Step 5: Pause for human approval before anything is sent
Add a Human node so a teammate reviews the draft. Give it a clear Label like Approve RFP reply and a Message that surfaces context, for example Draft answers ready for the RFP from {{ input.from }} (subject: {{ input.subject }}). Review the {{ draft.answers.length }} answers before sending. Set a Timeout (minutes) that matches your turnaround, set Urgency to Normal, and add the bid manager (or a Role or Team) as an Approval slot, which is the only required field. Turn on Email approvers if reviewers should be emailed in addition to seeing the item in the Approvals inbox.
When every slot is satisfied, the node outputs { approved: true, approvalId, outcome: "APPROVED" } and the workflow continues. A rejection or a timeout halts the run, so nothing is emailed to the prospect without a person accepting it. If you also want a heads-up in chat while approval is pending, place a Connector node on the Slack connector before the Human node in Direct mode using the send-message tool to your deals channel.
Step 6: Reply from your own domain with Resend
After the Human node, add a Connector node on the Resend connector in Direct mode and pick the send-email tool so the reply comes from your verified domain. Map the fields:
to:{{ input.replyTo }}(falls back to{{ input.from }}if the sender set no reply-to).from: your verified sender, for exampleBids Team <bids@yourdomain.com>.subject:Re: {{ input.subject }}.reply_to: your monitored bids address so the prospect's reply lands with your team.htmlortext: the approved answers. Build the body from{{ draft.answers }}, optionally formatting each item with a Transform node first.
A simple JSON payload for the tool inputs:
{
"from": "Bids Team ",
"to": "{{ input.replyTo }}",
"subject": "Re: {{ input.subject }}",
"reply_to": "bids@yourdomain.com",
"html": "{{ draft_html }}"
}
If you would rather not manage a sending domain, you can swap this final step for a Send Email node, which uses Spojit's built-in mail service; just make sure the prospect's address is on your organization email allowlist.
Tips
- Use the same embedding model for the persistent
company-capabilitiescollection that you used when you created it; mixing models degrades retrieval quality. - Keep the Result Count on the query step generous so multi-question RFPs retrieve enough source passages, but raise it gradually to control AI cost.
- Have Miraxa scaffold the skeleton for you: try "Build a workflow that watches a Mailhook, fetches the PDF attachment, embeds it into a transient collection, queries my company-capabilities collection, then waits for a Human approval before a Resend send-email." Then fine-tune fields in the properties panel.
- Use the
needs_human_inputflag in the draft schema to make the reviewer's job fast: anything flagged is exactly what the bid manager must fill in.
Common Pitfalls
- Forgetting Fail if no attachment matches on the Attachment node means a covering-note email with no PDF will run the AI steps against an empty transient collection. Turn it on.
- An unverified Resend domain causes
send-emailto fail; verify the domain under Connections first, or fall back to a Send Email node. - A rejected or timed-out Human approval halts the run, and there is no "on reject" branch. If reviewers may need to revise rather than reject, ask them to edit and re-run rather than reject.
- Large RFP files can exceed the 10 MB per-attachment or 25 MB per-run defaults; for oversized files, ask senders to compress or split the document.
Testing
Before pointing real prospect mail at it, email a short sample RFP PDF to your generated Mailhook address from an allowlisted account. Open the run in execution history and confirm the Attachment node produced a non-empty rfp_file, the embed step reported a chunk count on rfp_embedded, and the query step returned structured draft.answers. Approve the item from the Approvals inbox and check that Resend sent the reply to your own test inbox rather than a prospect. Once a couple of sample RFPs flow through cleanly, tighten the Subject regex and From allowlist, then start forwarding live RFP mail.