How to Extract Data from Emailed Invoice PDFs with a Transient Knowledge Collection
Point a vendor's invoice emails at a Spojit Mailhook, fetch the attached PDF, embed it into a one-run Transient Knowledge collection, and query that single document to pull clean structured fields, all in a single execution with no permanent storage.
What This Integration Does
Suppliers email PDF invoices in dozens of slightly different layouts, and keying the totals into your finance system by hand is slow and error prone. This workflow turns each inbound invoice email into structured data automatically. A Mailhook trigger receives the message, the Attachment node pulls the PDF bytes, and a Knowledge node embeds that one document into a Transient collection so a second Knowledge query can ask for the invoice number, dates, line items, and total and return them as reliable JSON.
The run is push based: it starts within seconds of an email arriving at your unique Mailhook address, with no mailbox or OAuth to connect. Each message produces one execution. The Transient collection is created fresh for that run, shared between the embed and query steps, and automatically discarded when the run completes, so no document or embedded content lingers in your workspace. Because Spojit deduplicates per message and the Mailhook never replies to the sender, re-sends of the same email do not double-process, and a failed run leaves no half-written knowledge behind.
Prerequisites
- A workspace where you can create a workflow in the Spojit designer.
- The ability to point a vendor's invoices at a new address, either by asking the vendor to add it, or by setting up a forwarding rule in the inbox that currently receives them.
- An AI model available for the Knowledge query step (used only for synthesis of the structured answer). No connector credentials are required for this base workflow.
- Optional: a destination connector (for example your accounting or database connector) if you want to push the extracted fields somewhere after this workflow proves out.
Step 1: Add a Mailhook trigger and generate its address
Create a new workflow and open the trigger node. Set Trigger Type to Mailhook. Optionally set an Address prefix (1 to 24 characters, default mh) such as invoices so the address is easy to recognize, then click Generate email address. Spojit produces a unique address in the form invoices-<random16>@mailhook.spojit.com. Copy it and give it to the vendor, or forward your invoice inbox to it.
The Mailhook fires whether the address is in To, Cc, or Bcc. Every inbound email is exposed to the rest of the workflow as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and the list of attachment references at {{ input.attachments }}. Each reference holds { id, filename, contentType }; the actual bytes are fetched in the next step.
Step 2: Filter to real invoice emails
Still in the trigger, tighten what is allowed to start a run so stray mail does not embed junk. Add a From allowlist containing the vendor's sending domain or address, and optionally a Subject regex so only invoice mail qualifies, for example:
(?i)invoice|inv-?\d+|statement
Emails that fail either filter are ignored and never start a run, which keeps your executions clean and avoids spending AI credits on newsletters or auto-replies.
Step 3: Fetch the PDF bytes with an Attachment node
Add an Attachment node after the trigger. This node only works in Mailhook workflows; the designer will refuse to save it without a Mailhook trigger. Configure it to grab the invoice PDF:
- Mode:
Singleso the node returns the first matching attachment as a single object. - Content type:
application/pdfto limit matches to PDFs. - Filename pattern: optional glob such as
*.pdforinvoice-*.pdfto be stricter. - Fail if no attachment matches: turn this on for an invoice workflow so an email with no PDF stops with a clear error instead of silently continuing.
In Single mode the output is { filename, contentType, size, content }, where content is the base64 file body. Assume this node's output is named attachment; you will reference its content as {{ attachment.content }} in the next step. Attachment limits default to 10 MB per attachment and 25 MB per run, which comfortably covers a typical invoice.
Step 4: Embed the invoice into a Transient collection
Add a Knowledge node set to Embed mode. This is where the single document is indexed for querying:
- Collection: choose Transient. A transient collection is created automatically for this run, shared with later Knowledge nodes in the same run, and cleaned up when the run finishes. You do not set a File Name or pick an embedding model for transient.
- Document Type:
PDFso the invoice is parsed correctly. - Document Input:
{{ attachment.content }}, the base64 body from the Attachment node. - Output Variable: name it (for example
embedResult) to capture the chunk count and metadata.
Transient is the right choice here because you only need to query this one invoice once. Nothing is written to your persistent Knowledge collections, and there is no cleanup to do later. If you instead want to keep every invoice for long-term lookup, see the persistent-collection approach in the related article on storing invoice data.
Step 5: Query the document for structured fields
Add a second Knowledge node set to Query mode, pointed at the same Transient collection so it reads the document embedded in Step 4:
- Collection: Transient (the same run's collection).
- Prompt: a plain-language instruction such as
Extract the invoice number, invoice date, due date, vendor name, currency, each line item with description quantity and amount, the subtotal, tax, and the grand total from this invoice. - Model: the AI model used to synthesize the answer.
- Result Count: leave at the default
5; a single short invoice rarely needs more. - Response Schema: provide a JSON schema so the output is structured and predictable rather than prose.
- Output Variable: name it
invoiceso downstream steps read{{ invoice.total }}and so on.
A Response Schema like the following forces clean fields you can map straight into another system:
{
"type": "object",
"properties": {
"invoiceNumber": { "type": "string" },
"invoiceDate": { "type": "string" },
"dueDate": { "type": "string" },
"vendorName": { "type": "string" },
"currency": { "type": "string" },
"lineItems": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"quantity": { "type": "number" },
"amount": { "type": "number" }
}
}
},
"subtotal": { "type": "number" },
"tax": { "type": "number" },
"total": { "type": "number" }
},
"required": ["invoiceNumber", "total"]
}
Step 6: Confirm receipt and route the result
Because a Mailhook never replies to the sender, add a Send Email node to acknowledge the invoice or to flag a problem. Set Recipients to {{ input.replyTo }} (or to your finance team), a templated Subject such as Invoice {{ invoice.invoiceNumber }} received, and a Body that echoes the parsed totals, for example We recorded invoice {{ invoice.invoiceNumber }} for {{ invoice.total }} {{ invoice.currency }}. Note that external recipients must be on your org allowlist under Settings > General > Email recipients.
Once the extraction is trustworthy, add a Connector node in Direct mode to push {{ invoice }} into your finance or database system, or a Human node before that to require approval on invoices above a threshold. You can also wrap the extraction in a Subworkflow so other workflows reuse it.
Tips
- Keep the embed and query Knowledge nodes both on Transient. They share the same per-run collection automatically, so the query sees exactly the document you just embedded and nothing else.
- Use a tight Response Schema with only the fields you actually need. Fewer fields means a smaller, cheaper, more reliable answer.
- Set the Attachment node's Content type to
application/pdfand turn on Fail if no attachment matches so emails without a real invoice fail loudly instead of embedding the wrong file. - Let Miraxa, the intelligent layer across your automation, scaffold the skeleton. Try a prompt like "Build a workflow that watches a mailhook, extracts the PDF invoice, and returns the invoice number and total as JSON," then fine-tune each node in the properties panel.
Common Pitfalls
- Expecting bytes straight from the trigger.
{{ input.attachments }}holds references only. You must add the Attachment node to getcontent. - Querying a persistent collection by mistake. If the query node points at a saved collection instead of Transient, it will not see this run's freshly embedded invoice. Set both Knowledge nodes to Transient.
- Oversized files. Attachments default to 10 MB each and 25 MB per run. A scanned, image-heavy invoice can exceed this; ask the vendor for a text-based PDF where possible.
- No From allowlist or Subject regex. Without filters, any mail to the address starts a run and consumes AI credits. Lock the trigger down to the vendor before going live.
- Treating dates and totals as guaranteed. Vendors format dates and currencies inconsistently. Validate
{{ invoice.total }}and date fields with a Condition node before writing them anywhere downstream.
Testing
Before pointing the vendor at the address, send yourself a real sample invoice PDF to the generated Mailhook address from an allowlisted sender. Open the run in execution history and check each node in order: the trigger shows the email with one attachment reference, the Attachment node shows a non-zero size and a content value, the embed node reports a chunk count, and the query node returns JSON matching your Response Schema. Confirm the Transient collection does not appear in your Knowledge section afterward, which proves it was discarded. Once a few varied invoice layouts extract correctly, add your real filters and downstream connector and switch the vendor over.