How to Validate Emailed PDF Attachments Before Processing
Build a Spojit workflow where a Mailhook receives a PDF, the Attachment node fetches it under strict size and content-type filters, the PDF connector checks its page count, and a Human node approves it before any downstream handling runs.
What This Integration Does
When you accept documents by email (vendor invoices, signed contracts, purchase orders), most of the work is rejecting the bad ones before they reach the expensive steps. A teammate forwards a 40 MB scan, a sender attaches the wrong file type, or a one-page cover sheet arrives where a full document was expected. This workflow puts a validation gate in front of your real processing: it only fetches genuine PDF bytes, confirms the document looks right, and asks a person to sign off before anything irreversible happens. That keeps junk out of your archive, your CRM, or your knowledge base, and gives you an audit trail of who approved each document.
The run model is push-based. A Mailhook trigger gives you a unique address, and any mail sent to it starts a run within seconds with the parsed message available as {{ input }}. The Attachment node then fetches the raw bytes of the matching PDF, the PDF connector's get-info tool reads its page count, a Condition node screens out documents that fail your rules, and a Human node pauses the run until an approver responds in the Approvals inbox. Mailhook runs are always asynchronous (there is no reply to the sender), each message is deduplicated, and received emails are retained for 30 days. A rejected or timed-out approval halts the run, so nothing downstream executes unless a person explicitly approves.
Prerequisites
- A Spojit workspace where you can create workflows in the Workflow Designer.
- An email source you control that can send or forward PDFs to a generated address (a vendor notification, a mailbox forwarding rule, or a system mailer).
- At least one approver: a User, Role, or Team in your workspace to assign to the Human node's approval slot.
- No connections to configure for the core gate: the Mailhook trigger needs no mailbox or OAuth, and the PDF connector is a built-in utility that runs in-process with no auth.
- A clear rule for what counts as valid (for example: must be
application/pdf, at most 10 MB, and at least 2 pages).
Step 1: Add a Mailhook trigger and generate the address
Create a new workflow, then on the Trigger node set Trigger Type to Mailhook. Optionally set an Address prefix (1 to 24 characters, default mh), then click Generate email address. Spojit produces a unique address of the form <prefix>-<random>@mailhook.spojit.com. Copy it and point your sender or forwarding rule at it. To cut noise, add an optional From allowlist and a Subject regex so only expected senders and subjects start a run. Once mail arrives, the trigger output is available as {{ input }}, including {{ input.from }}, {{ input.subject }}, {{ input.replyTo }}, and the attachments list where each entry is { id, filename, contentType }.
Step 2: Fetch the PDF bytes with the Attachment node
Add an Attachment node directly after the trigger. The designer only allows an Attachment node in a Mailhook workflow, so this is where you turn the attachment references into real bytes. Set Mode to Single so you get the first match back as one object. Apply tight filters so unrelated files never get fetched:
- Content type:
application/pdf - Filename pattern:
*.pdf - Max size:
10485760(10 MB), and a sensible Min size such as1024to skip empty stubs - Fail if no attachment matches: turn this on so a mail with no valid PDF halts here instead of continuing with nothing
In Single mode the output variable resolves to:
{
"filename": "invoice-4821.pdf",
"contentType": "application/pdf",
"size": 384112,
"content": "JVBERi0xLjQKJ..."
}
The content field is base64, which is exactly what the next step needs. Remember the per-attachment limit is 10 MB and the per-run limit is 25 MB by default.
Step 3: Read the page count with the PDF connector
Add a Connector node in Direct mode and choose the PDF connector with the get-info tool. Direct mode is the right choice here: it is deterministic, calls exactly one tool, and costs no AI credits. Map the tool's single input, pdf, to the base64 bytes from Step 2 using the attachment output variable, for example {{ attachment.content }}. The tool returns document metadata you can branch on:
{
"pageCount": 6,
"title": "Statement of Work",
"author": "Acme Legal",
"creationDate": "2026-06-18T09:14:00.000Z",
"pages": [
{ "pageNumber": 1, "width": 612, "height": 792, "rotation": 0 }
]
}
The key field for validation is pageCount. If the bytes are not a real PDF, this step fails rather than returning a bogus count, which is itself a useful signal that the file was malformed.
Step 4: Gate on the page count with a Condition node
Add a Condition node to enforce your document rule. Reference the page count from the previous step, for example {{ pdf_info.pageCount }}, and test it against your threshold (such as greater than or equal to 2). The Condition node exposes a True and a False output. Wire the True branch onward to the approval step. On the False branch, add a Send Email node that notifies the sender or your intake team that the document was rejected, templating the reason with {{ input.subject }} and {{ pdf_info.pageCount }} and addressing it to {{ input.replyTo }}. Because the Mailhook never replies automatically, Send Email is how you give a human on the other end any feedback.
Step 5: Pause for sign-off with a Human node
On the True branch, add a Human node so a person approves the document before downstream handling. Fill in:
- Label: a short title such as
Approve incoming PDF - Message: the context approvers need, for example
Validate {{ attachment.filename }} ({{ pdf_info.pageCount }} pages) from {{ input.from }} - Approval slots: the only required field. Add one slot and put a User, Role, or Team atom in it. Any atom satisfies its slot, and approval completes when every slot is satisfied.
- Timeout (minutes): optional. Leave blank for no deadline, or set a value so stale requests time out (a timeout is treated as a reject and halts the run).
- Email approvers: turn on to email the first 10 approvers; otherwise they act in the Approvals inbox at
/approvalsvia the dashboard widget and menu badge.
On approval the node continues and outputs { approved: true, approvalId, outcome: "APPROVED" }. A reject or timeout halts the run, so any nodes you place after the Human node only ever run for approved documents.
Step 6: Hand off the approved document
After the Human node, do whatever your process requires now that the PDF is validated and signed off. Because you still hold the base64 content from Step 2, you can feed it straight into a downstream node: attach it to a Send Email node to forward it on, embed it into a collection with a Knowledge node (set Document Type to PDF and Document Input to {{ attachment.content }}), or pass it to a Subworkflow node that already contains your shared filing logic. Keeping the heavy handling behind the approval gate means it only ever runs against documents that passed every check.
Tips
- Filter as early as possible: the Attachment node's Content type and Min/Max size filters stop oversized or wrong-type files before they ever reach the PDF connector, which keeps runs fast and cheap.
- Use a From allowlist and Subject regex on the trigger to keep stray mail from starting runs at all; this pairs well with the per-attachment 10 MB and per-run 25 MB limits.
- If you expect several PDFs per email, switch the Attachment node to
Multiplemode and place a Loop node over{{ attachment.attachments }}, running the PDF check and approval per document. - Let Miraxa, the intelligent layer across your automation, scaffold the shape for you with a prompt like "Build a workflow that watches a Mailhook, fetches the PDF attachment, checks its page count, and pauses for human approval," then fine-tune each node in the properties panel.
Common Pitfalls
- Leaving Fail if no attachment matches off (its default): the run continues with an empty result and the PDF step fails confusingly. Turn it on so a missing PDF halts cleanly at the Attachment node.
- Expecting a reply to the sender: Mailhook runs are always asynchronous and never auto-reply. If you need to tell the sender the document was rejected, add a Send Email node addressed to
{{ input.replyTo }}. - Assuming a rejected approval branches: the Human node has no "on reject" path. A reject or timeout halts the run entirely, so put every downstream action after the node, never on a hoped-for reject branch.
- Trusting the filename alone: an attachment named
.pdfis not guaranteed to be a valid PDF. Theget-infostep is what actually proves the bytes parse, so keep it in the chain even when the filename pattern already matches.
Testing
Validate on a tiny scope first. Send one small, known-good PDF to the generated Mailhook address and confirm the run reaches the Human node, then approve it from the Approvals inbox and check that the downstream step ran. Next, send a non-PDF or an oversized file and confirm the Attachment node halts (with Fail if no attachment matches on) or the Condition node routes to your rejection email. Finally, reject one request and confirm the run stops with no downstream effects. Use the execution history to inspect {{ input }}, the Attachment output, and the pageCount at each step before pointing real vendor mail at the address.