How to Approve and Send a Quote PDF from a Mailhook Request
Receive an internal quote request by email, fetch the draft quote PDF, have Miraxa read off the total and terms, route any deep discount to a sales manager for sign-off, then send the approved quote to the customer with Resend and log the deal stage in Monday.com.
What This Integration Does
Sales teams often draft a quote as a PDF, then forward it internally with a line like "please approve and send to the customer." This workflow turns that informal handoff into a controlled process in Spojit. A Mailhook trigger gives you a dedicated address to forward quote requests to. An Attachment node pulls the draft quote PDF straight off the email, a Connector node in Agent mode reads the quote total and terms, and a Human approval node holds anything over your discount threshold until a sales manager signs off in the Approvals inbox. Only after approval does the quote reach the customer, so no oversized discount goes out the door unreviewed.
The run is fully push based: forwarding an email to the Mailhook address starts a run within seconds, with no mailbox or polling involved. The draft PDF flows from the Attachment node into Agent-mode review, then into the approval gate, and finally into a Resend send-email call that delivers the quote to the customer and a Monday.com update-item call that moves the deal forward. Each forwarded message produces one run and is deduplicated, so re-forwarding the same message will not double-send. If the manager rejects or the approval times out, the workflow halts before anything is sent to the customer.
Prerequisites
- A Resend connection with a verified sending domain (the customer-facing quote is sent from your own address). See the Resend connector article.
- A Monday.com connection, plus the
board_idof your sales pipeline board and the column IDs you use for deal stage. See the Monday.com connector article. - A sales manager set up as a User, Role, or Team you can target in an approval slot. See Using Human Approval Nodes.
- A draft quote process where the quote PDF is attached to the forwarded email (under 10 MB per attachment).
Step 1: Receive the quote request with a Mailhook trigger
Add a Trigger node and set Trigger Type to Mailhook. Set an Address prefix such as quotes, then choose Generate email address to mint an address like quotes-9f3a2b7c1d4e5f60@mailhook.spojit.com. Copy it and have your reps forward (or Cc) quote requests to it. To keep the workflow scoped to your own team, set a From allowlist of your internal domain and an optional Subject regex such as (?i)quote.
The trigger exposes the message as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and {{ input.attachments }} (each attachment is a reference of { id, filename, contentType }). The Mailhook is always asynchronous, so the requester gets no automatic reply; you will reply explicitly later with a Send Email node if you want to confirm receipt.
Step 2: Fetch the draft quote PDF with an Attachment node
Add an Attachment node. This node only works downstream of a Mailhook trigger. Set Mode to Single so you get one object back, set the Content type filter to application/pdf, and set a Filename pattern such as *quote*.pdf so a stray signature image is never picked up. Turn on Fail if no attachment matches so a request with no quote attached stops here instead of sending an empty quote.
In Single mode the node outputs { filename, contentType, size, content }, where content is the base64 PDF bytes. Name the output something like quote_pdf so later steps can reference {{ quote_pdf.content }} and {{ quote_pdf.filename }}. Keep in mind the default limits of 10 MB per attachment and 25 MB per run.
Step 3: Extract the quote total and terms with a Connector node in Agent mode
Add a Connector node on the pdf connector in Direct mode and use extract-text against {{ quote_pdf.content }} to pull the readable text out of the PDF. Name the result quote_text.
Then add a second Connector node in Agent mode and let Miraxa, the intelligent layer across your automation, read the quote and report the numbers you need to gate on. Define a Response Schema so the output is reliable structured JSON, and write a prompt that names the fields you want.
Prompt:
Read this quote and return its total amount, currency, the
list discount percentage applied, the customer name, the
customer email, and the payment terms. If a value is missing,
return null for it.
Quote text:
{{ quote_text.text }}
Response Schema:
{
"type": "object",
"properties": {
"customerName": { "type": "string" },
"customerEmail": { "type": "string" },
"total": { "type": "number" },
"currency": { "type": "string" },
"discountPct": { "type": "number" },
"terms": { "type": "string" }
},
"required": ["total", "discountPct", "customerEmail"]
}
Name the output review so downstream steps can read {{ review.discountPct }}, {{ review.total }}, {{ review.customerEmail }}, and {{ review.terms }}.
Step 4: Gate deep discounts behind a sales manager with a Human approval node
Add a Condition node that checks whether {{ review.discountPct }} is greater than your threshold (for example 15). On the true branch, add a Human approval node so a manager must sign off before the quote goes out. On the false branch, skip straight to Step 5 and send the quote without manual review.
Configure the Human node with a clear Label ("Approve discounted quote") and a templated Message and Notification body:
Quote for {{ review.customerName }} is {{ review.currency }} {{ review.total }}
at a {{ review.discountPct }}% discount.
Terms: {{ review.terms }}
Requested by {{ input.from }}.
Add one Approval slot and put your sales manager User, Role, or Team in it as an atom (any atom satisfies the slot; every slot must be satisfied for approval to complete). Set Urgency to High for customer-facing quotes and a Timeout in minutes if a stale quote should not linger. Optionally turn on Email approvers. 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 reaches the customer.
Step 5: Send the approved quote to the customer with Resend
Add a Connector node on the resend connector in Direct mode and pick send-email. Map the fields from your extracted data and the fetched PDF:
to: {{ review.customerEmail }}
subject: Your quote from Acme Sales
text: Hi {{ review.customerName }},
Please find your quote attached. Total:
{{ review.currency }} {{ review.total }}.
Terms: {{ review.terms }}.
attachments:
- filename: {{ quote_pdf.filename }}
content: {{ quote_pdf.content }}
The attachments field on send-email takes a filename plus base64 content, so the bytes from your Attachment node in Step 2 drop straight in. Because Resend sends from your own verified domain, the customer sees the quote as coming from your address rather than from Spojit's built-in mail service. If you only need an internal confirmation rather than a domain-branded send, you can use a Send Email node instead and reply to {{ input.replyTo }}.
Step 6: Log the deal stage in Monday.com
Add a Connector node on the monday connector in Direct mode and pick update-item. Set board_id to your sales pipeline board and item_id to the deal you are advancing (resolve it earlier with a list-items call keyed on the customer if you do not already carry the id). Pass column_values as a JSON object that moves the deal stage and records the approved total.
board_id: 1234567890
item_id: 9876543210
column_values: {
"status": { "label": "Quote Sent" },
"numbers": "{{ review.total }}",
"text_terms": "{{ review.terms }}"
}
Use your own column IDs (open the board's columns to confirm them). If you also want a running note on the item, add a second Connector node with create-update and a body like Quote approved by manager and sent to {{ review.customerEmail }}.
Tips
- Ask Miraxa to scaffold the skeleton: "Build a workflow that watches a mailhook, fetches the PDF attachment, extracts the total and discount with an Agent-mode node, routes discounts over 15% to a Human approval node, then sends the PDF with the resend send-email tool." Then fine-tune each node in the properties panel.
- Set the discount threshold in the Condition node, not in the prompt, so you can change the policy without re-tuning Miraxa.
- Set the Human node Urgency to
Highand a sensible Timeout so a forgotten approval does not leave a customer waiting indefinitely. - If a request can carry several PDFs, switch the Attachment node to
Multipleand Loop over{{ quote_pdf.attachments }}, but keep the per-run total under the 25 MB limit.
Common Pitfalls
- The designer refuses to save an Attachment node unless the workflow starts from a Mailhook trigger. An Email trigger will not work here.
- Without Fail if no attachment matches turned on, a request with no quote attached flows through and can send an empty email. Turn it on, or guard with a Condition on
{{ quote_pdf.size }}. - Rejections and timeouts halt the run; there is no "on reject" branch. If you need to notify the requester of a rejection, do it before the customer-facing send only when approved, and rely on the manager's note in the Approvals inbox for declines.
- Resend will not send from a domain you have not verified. Confirm your sending domain with
get-domainorverify-domainfirst, or the customer send fails. - Mailhook addresses are scoped by your From allowlist and Subject regex; if test forwards never trigger a run, check those filters and the exact generated address.
Testing
Before pointing live quote requests at the workflow, forward one test email with a small sample PDF to the generated Mailhook address. Watch the run in execution history: confirm the Attachment node returns a non-zero size, the Agent-mode node returns a populated review object, and the Condition routes correctly for both a low and a high discount sample. For the high-discount sample, open /approvals, approve as the manager, and verify the Resend send lands with the PDF attached and the Monday.com item moves to your "Quote Sent" stage. Keep the threshold high and the recipient set to an internal test address until you trust the extraction.