How to Turn an Emailed Proposal PDF into a Monday Project and a Knowledge Archive
Forward a signed proposal PDF to a Spojit mailhook address and have Spojit pull the file, read its scope and value with AI, open a project item in Monday.com, post a kickoff note to Slack, and file the contract in a searchable knowledge archive.
What This Integration Does
Professional-services teams win work by email: a client signs a statement of work or proposal and sends back the PDF. Someone then re-keys the project name, scope, and contract value into your project tool, drops the file somewhere shared, and pings the delivery team. This tutorial replaces that manual handoff. The proposal lands at a dedicated address, Spojit reads the document, creates the Monday.com project item with the value already filled in, announces it in Slack, and keeps the signed contract in a persistent knowledge collection you can ask questions of later ("what was the agreed scope for the Acme rollout?").
The run model is push-based and asynchronous. A Mailhook trigger fires within seconds of mail arriving at its unique address, with no mailbox or polling involved. The Attachment node fetches the PDF bytes, a Knowledge node in Embed mode loads the document into a transient collection scoped to that single run, a second Knowledge node in Query mode extracts structured fields with AI, a Connector node creates the Monday item, and a final Embed node copies the contract into a persistent archive. Each inbound email runs once (Spojit deduplicates per message), and re-running is safe to test because you control which address mail is sent to.
Prerequisites
- A Monday.com connection added under Connections, plus the target board's ID. Open the board in Monday.com, or use the monday connector's
list-boardstool once to find theboardIdand the group and column IDs you want to populate. - A Slack connection added under Connections, and the channel where kickoff notes should be posted (for example
#new-projects). - A persistent Knowledge collection to hold signed contracts. Create it under the Knowledge section of the sidebar with New Collection (for example
signed-contracts); the embedding model is fixed when you create it. - A workflow with a Mailhook trigger. The Attachment node only saves inside a workflow whose trigger is a Mailhook.
Step 1: Generate the mailhook address
Create a new workflow and add a Trigger node. Set Trigger Type to Mailhook. Set an optional Address prefix (1 to 24 characters, for example proposals), then click Generate email address. Spojit produces a unique address such as proposals-3f9c1a8b2d7e4061@mailhook.spojit.com. Copy it and either give it to clients as your "send signed proposals here" address or add a forwarding rule in your shared inbox that copies proposal emails to it.
Optionally tighten the trigger with a From allowlist (only accept mail from known client domains) and a Subject regex (for example to require the word "proposal" or "SOW"). The trigger fires whether the address is in To, Cc, or Bcc. After the trigger runs, the email is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and the {{ input.attachments }} references.
Step 2: Fetch the PDF with an Attachment node
Add an Attachment node after the trigger. This node reads the actual bytes of the file the Mailhook trigger only referenced. Configure it to pick the proposal:
- Mode:
Single, so you get one object back rather than a list. - Content type:
application/pdf. - Filename pattern:
*.pdf(a glob), so a stray signature image in the same email is ignored. - Fail if no attachment matches: turn this on so a proposal email that arrives with no PDF stops cleanly instead of creating an empty project.
In Single mode the node outputs { filename, contentType, size, content }, where content is the base64 file body. Note the node's output variable name (for example attachment) so you can reference {{ attachment.content }} later. Keep in mind the default limits of 10 MB per attachment and 25 MB per run.
Step 3: Embed the proposal into a transient collection
Add a Knowledge node and set its mode to Embed. This loads the proposal into a private, single-run collection so the AI can read it in the next step without permanently storing a draft:
- Collection: choose Transient. A transient collection is created for this run only, shared across nodes in the same run, and cleaned up automatically when the run finishes. No file name or embedding model is needed.
- Document Type:
PDF. - Document Input:
{{ attachment.content }}(the base64 body from Step 2).
The node outputs a chunk count and metadata; you do not need to reference it directly, because the next node queries the same transient collection by selecting Transient again.
Step 4: Extract scope and value as structured JSON
Add a second Knowledge node and set its mode to Query. This is where Miraxa, the intelligent layer across your automation, reads the embedded proposal and returns clean fields you can map onto a Monday item:
- Collection: Transient (the same run-scoped collection you embedded into in Step 3).
- Prompt: describe exactly what you want, for example "Extract the client/company name, the project title, a one-paragraph scope summary, the total contract value as a number, the currency, and the proposed start date from this signed proposal."
- Result Count: leave at the default of 5 (enough chunks for a short proposal).
- Response Schema: supply a JSON schema so the output is reliable structured data rather than prose. For example:
{
"type": "object",
"properties": {
"clientName": { "type": "string" },
"projectTitle": { "type": "string" },
"scopeSummary": { "type": "string" },
"contractValue":{ "type": "number" },
"currency": { "type": "string" },
"startDate": { "type": "string" }
},
"required": ["clientName", "projectTitle", "contractValue"]
}
Set the Output Variable to something like proposal. Downstream you can then read {{ proposal.clientName }}, {{ proposal.projectTitle }}, {{ proposal.contractValue }}, and the rest. (If you prefer to do this extraction with a Connector node in Agent mode, the same Response Schema idea applies there too.)
Step 5: Create the Monday.com project item
Add a Connector node on the monday connector in Direct mode and choose the create-item tool. Direct mode is deterministic and uses no AI credits, which is what you want for a predictable single create. Map the fields:
boardId: your projects board ID (for example1234567890).name:{{ proposal.projectTitle }}(or combine client and title, for example{{ proposal.clientName }} - {{ proposal.projectTitle }}).groupId: optional, the group on the board where new projects should land (for examplenew_projects).columnValues: an object keyed by your board's column IDs, so the contract value and start date arrive pre-filled. For example:
{
"numbers": {{ proposal.contractValue }},
"text": "{{ proposal.scopeSummary }}",
"date4": { "date": "{{ proposal.startDate }}" }
}
Replace numbers, text, and date4 with the real column IDs from your board (the list-boards tool returns them). The tool returns the new item's id and name; capture the output variable (for example mondayItem) so you can reference {{ mondayItem.create_item.id }} in the next steps.
Step 6: Post a kickoff note to Slack and a Monday update
Add a Connector node on the slack connector in Direct mode with the send-message tool. Set the channel (for example #new-projects) and a templated message:
:tada: New project booked: *{{ proposal.projectTitle }}* for {{ proposal.clientName }}
Contract value: {{ proposal.currency }} {{ proposal.contractValue }}
Scope: {{ proposal.scopeSummary }}
If you also want the detail recorded on the item itself, add a second Connector node on the monday connector with the create-update tool, setting itemId to {{ mondayItem.create_item.id }} and body to the same scope summary. This posts the proposal summary as a comment on the new Monday item so the delivery team sees the context without opening the PDF.
Step 7: Archive the signed contract in a persistent collection
Add a final Knowledge node in Embed mode to file the contract permanently for future lookups:
- Collection: your persistent
signed-contractscollection (not Transient this time). - File Name: a stable, descriptive name so it is easy to find and so re-sends overwrite cleanly, for example
{{ proposal.clientName }}-{{ proposal.projectTitle }}.pdf. - Document Type:
PDF. - Document Input:
{{ attachment.content }}(the same base64 body from Step 2).
Because this collection is workspace-scoped and persistent, any later workflow can run a Query-mode Knowledge node against signed-contracts to answer questions like "what was the agreed scope for the Acme rollout?" without re-processing the original email. Optionally, end the workflow with a Send Email node back to {{ input.replyTo }} confirming the proposal was received and the project created.
Tips
- Keep the extraction (Step 4) and the create (Step 5) separate: extract once into
{{ proposal }}, then map fields deterministically. This keeps AI cost to a single Query node and makes the Monday create predictable. - Use the SAME embedding model for embed and query of a persistent collection. The
signed-contractscollection's model is fixed at creation, so create it before you reference it in Step 7. - Set the Monday
numberscolumn from{{ proposal.contractValue }}as a raw number (no currency symbol or commas) so Monday accepts it; keep the currency as a separate text or status column. - Use the From allowlist and Subject regex on the trigger so internal CC chatter or non-proposal mail to the address does not start a run.
Common Pitfalls
- Attachment node with no Mailhook trigger. The designer refuses to save an Attachment node unless the workflow's trigger is a Mailhook. Build the trigger first.
- Wrong column IDs. Monday's
columnValuesare keyed by column ID, not by the column's display label. Runlist-boardsonce to get the exact IDs; a wrong key is silently ignored. - Querying before embedding. The Query node in Step 4 must select Transient and run after the Embed node in Step 3 in the same run, or there is nothing to read.
- Oversized proposals. Attachments default to a 10 MB per-file and 25 MB per-run limit. A large scanned contract may exceed this; ask the client to send a text-based PDF or raise the limits in the Attachment node.
- Missing fields in the proposal. If a proposal omits the value or start date, only
clientName,projectTitle, andcontractValueare required by the schema. Consider a Condition node to route incomplete proposals to a human instead of creating a half-filled item.
Testing
Before pointing client mail at the address, send a sample signed proposal PDF from your own inbox to the generated mailhook address and watch the run in the execution history. Confirm the Attachment node returned a non-zero size, the Query node's {{ proposal }} output has the right client name and value, and a test item appeared on your Monday board with the value pre-filled. Point the first runs at a throwaway test board and a private Slack channel; once the mapping is correct, switch boardId and the Slack channel to production and turn on the From allowlist. Received emails are retained for 30 days, so you can re-send the same test proposal to iterate.