How to Ingest Emailed Data Exports into MongoDB with a Mailhook

Point a vendor's scheduled CSV export email at a Spojit mailhook, parse the attached file, and insert every row into a MongoDB collection automatically.

What This Integration Does

Many vendors and internal systems email a recurring data export: a nightly sales file, a supplier price list, a payroll extract, or a marketing report attached as a CSV. Re-keying that file or hand-importing it into a database wastes time and invites mistakes. This Spojit workflow gives the export email its own dedicated address. When the file lands, Spojit pulls the attachment, turns the CSV into structured records, and writes them straight into MongoDB so your team or downstream reports always read from fresh data.

The run model is push-based. A Mailhook trigger generates a unique address; any mail sent to it starts a run within seconds, with no mailbox or polling involved. The Attachment node fetches the bytes of the CSV referenced by that email, the csv connector converts those bytes into an array of row objects, and the mongodb connector inserts them. Each email is deduplicated per message, so the same export will not be ingested twice if a sender retries. Because every run inserts the rows it received, re-running an old email re-inserts those rows: design your collection and pitfalls handling (below) with that append behavior in mind.

Prerequisites

  • A MongoDB connection added in Spojit under Connections → Add connection, with write access to the target database and collection.
  • The exact name of the destination MongoDB collection (for example vendor_exports).
  • A sample of the vendor's export CSV so you know its column headers and delimiter.
  • The ability to set the vendor's export email recipient, or a forwarding rule that sends a copy of the export to a Spojit mailhook address.
  • Permission to create workflows in your workspace. The csv and json connectors are built in and need no connection.

Step 1: Add the Mailhook trigger and generate an address

Create a new workflow and open its Trigger node. Set Trigger Type to Mailhook. Optionally set an Address prefix (1 to 24 characters, default mh) so the address is recognizable, for example exports. Click Generate email address to produce a unique address like exports-3f9a2b1c8d4e7a6f@mailhook.spojit.com, then copy it and point the vendor's export at it (or create a mail forwarding rule to it). To reduce noise, add an optional From allowlist with the vendor's sending address and a Subject regex such as Daily Export so only the right messages start a run. The trigger output is available downstream as {{ input }}, including {{ input.subject }}, {{ input.from }}, and the {{ input.attachments }} references.

Step 2: Fetch the CSV with an Attachment node

Add an Attachment node after the trigger. The designer only allows this node when the trigger is a Mailhook. Set Mode to Single so the node returns the first matching file as an object. Narrow the match with a Filename pattern glob like *.csv (or something tighter such as export-*.csv), and optionally a Content type filter of text/csv. Turn on Fail if no attachment matches so a malformed email does not silently produce an empty run. The node output looks like this:

{
  "filename": "daily-export-2026-06-21.csv",
  "contentType": "text/csv",
  "size": 18452,
  "content": "<base64-encoded CSV bytes>"
}

The content field is base64, and you will decode it in the next step before parsing. Attachment limits default to 10 MB per attachment and 25 MB per run.

Step 3: Decode the attachment bytes to text

Add a Connector node in Direct mode targeting the encoding connector and the base64-decode tool. Map its input to the attachment content with {{ attachment.content }} (use the variable name of your Attachment node). This converts the base64 payload back into the raw CSV text that the parser expects. Bind the result to a variable such as csv_text so the next step can reference {{ csv_text }}. If the export is delivered already as plain text rather than base64, you can skip this step and feed {{ attachment.content }} directly into the parser, but the Attachment node returns base64, so decoding first is the reliable default.

Step 4: Convert the CSV into row objects

Add a Connector node in Direct mode using the csv connector and the to-json tool. Map the csv input to {{ csv_text }} from the previous step. Leave header set to true so the first row becomes the keys of each object, and set delimiter to match the file (a comma , by default, or a tab/semicolon if the vendor uses one). The tool returns an array of objects plus a count, for example:

{
  "json": [
    { "order_id": "1001", "sku": "ABC-1", "qty": "3", "total": "59.97" },
    { "order_id": "1002", "sku": "XYZ-9", "qty": "1", "total": "12.50" }
  ],
  "count": 2
}

If you need to drop columns, rename keys, or coerce numeric strings before storing, add a Transform node here, or use the json connector tools such as pick, omit, or set to reshape each record. Bind the parsed array to a variable like rows.

Step 5: Insert the rows into MongoDB

Add a Connector node in Direct mode using the mongodb connector and the insert-documents tool. Set collection to your destination (for example vendor_exports) and map documents to the parsed array {{ rows.json }}. The tool inserts one document per row and returns the count and generated identifiers. To make ingest traceable, add a Transform or json set step before this node that stamps each row with provenance fields, for example:

{
  "source": "mailhook",
  "subject": "{{ input.subject }}",
  "receivedAt": "{{ input.receivedAt }}",
  "fileName": "{{ attachment.filename }}"
}

For larger files you can fan out across a Loop node, inserting batches of rows per iteration, but a single insert-documents call handles the whole array in one step for most exports.

Step 6: Confirm and notify

Add a Send Email node to close the loop. Set Recipients to your data team and a Subject like Imported {{ rows.count }} rows from {{ input.subject }}, with a short body summarizing the file name and row count. Because a Mailhook trigger is always asynchronous and never replies to the sender, Send Email is how you surface success. If you want to acknowledge the original sender instead, address the email to {{ input.replyTo }}. Set If sending fails to Continue anyway so a notification problem never rolls back a successful insert.

Tips

  • Use a descriptive Address prefix per source so you can tell vendor mailhooks apart at a glance, and keep one workflow per export format.
  • Set the Attachment node Filename pattern as tightly as the file naming allows; it prevents signature images or footer logos from being picked up instead of the data file.
  • CSV values arrive as strings. Coerce numbers and dates with a Transform node or the json connector before insert if downstream queries need typed fields.
  • Stamp each document with {{ input.messageId }} so you can identify and clean up the exact batch a given email produced.

Common Pitfalls

  • Re-running an old email re-inserts its rows. If duplicate ingests are a risk, store {{ input.messageId }} on each document and use find-documents to check before inserting, or switch to update-documents with an upsert key.
  • Wrong delimiter produces a single mangled column. Open a sample file first and set the delimiter on to-json to match exactly.
  • Files over the 10 MB per-attachment or 25 MB per-run limit will not be fetched. For very large exports, have the vendor split the file or deliver it to FTP instead of email.
  • Received emails are retained for 30 days, so re-processing an export older than that is not possible. Capture the data into MongoDB promptly rather than relying on the inbox as an archive.

Testing

Before pointing the live vendor feed at it, send a small test CSV with two or three rows from an allowlisted address to the generated mailhook address. Open the run in execution history and check each step in order: the Attachment node should show the correct filename and a non-zero size, the to-json step should report the expected count, and insert-documents should return that same number of inserted documents. Query the collection with find-documents to confirm the rows landed with the shape you expect, then widen the allowlist and subject filter to the real sender. You can also ask Miraxa, the intelligent layer across your automation, "Why did my last run fail?" to investigate any step that errors.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.