How to Log Emailed Timesheet Submissions into a Database with a Mailhook

Give staff a dedicated email address to send timesheet CSVs to, then have Spojit parse every attachment and insert each row into a MySQL timesheet table automatically.

What This Integration Does

Plenty of teams still collect time data the low-tech way: a staff member fills in a spreadsheet, exports it as a CSV, and emails it in at the end of the week. Re-keying those rows into a payroll database by hand is slow and error prone. This Spojit workflow removes the manual step entirely. You publish one mailhook address, staff email their timesheet CSV to it, and each row lands in your MySQL timesheets table within seconds, ready for payroll to query.

The workflow runs on a Mailhook trigger, so it fires the moment a message arrives at the address: there is no mailbox to connect and no polling delay. The Attachment node pulls the CSV bytes off the email, a Connector node using the csv connector turns those bytes into an array of row objects, and a Connector node using the mysql connector inserts the rows. Each email is processed once (Spojit deduplicates per message), runs are async (the sender gets no automatic reply unless you add a Send Email node), and a fresh email always starts a fresh run, so re-sending a corrected file simply inserts the corrected rows.

Prerequisites

  • A mysql connection added under Connections, pointing at the database that holds your timesheet table.
  • A destination table, for example timesheets, with columns that match your CSV headers (such as employee_id, work_date, hours, project). The csv connector is a built-in utility, so it needs no connection.
  • Agreement on a fixed CSV header row so the column names map cleanly to your table columns.
  • Permission to create a workflow in your workspace. No mailbox, OAuth, or signing connection is required for a mailhook.

Step 1: Add a Mailhook trigger and generate the 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 timesheets so the address is recognisable, then click Generate email address. Spojit creates a unique address in the form timesheets-a1b2c3d4e5f6g7h8@mailhook.spojit.com. Copy it and share it with your staff. To cut down on stray mail, add a From allowlist of your staff domains and an optional Subject regex (for example (?i)timesheet). When the workflow runs, the email is available as {{ input }}, including {{ input.from }}, {{ input.subject }}, {{ input.replyTo }}, and the {{ input.attachments }} references.

Step 2: Fetch the CSV with an Attachment node

Add an Attachment node directly after the trigger. The designer only allows this node when the workflow starts with a Mailhook trigger. Set Mode to Single so you get the first matching file as one object, and set a Filename pattern of *.csv so only spreadsheet exports are picked up. Optionally narrow further with a Content type filter of text/csv. Turn on Fail if no attachment matches so an email with no CSV stops the run instead of inserting nothing. With Single mode the node outputs:

{
  "filename": "week-23.csv",
  "contentType": "text/csv",
  "size": 1840,
  "content": "<base64-encoded bytes>"
}

Name the output variable attachment so the file content is available downstream as {{ attachment.content }}.

Step 3: Decode the CSV bytes to text

The Attachment node returns content as base64, but the CSV tool expects a plain CSV string. Add a Connector node in Direct mode using the encoding connector and the base64-decode tool. Map its input to {{ attachment.content }} and name the output variable decoded. This gives you the raw CSV text, which you will pass to the parser in the next step. If your staff always send small UTF-8 files this single decode step is all the preparation the data needs.

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 the decoded text from the previous step (for example {{ decoded.result }}), leave header set to true so the first row is treated as column names, and keep delimiter as , unless your exports use semicolons. Name the output variable parsed. The tool returns an items array where each row is an object keyed by your CSV headers, plus a count:

{
  "items": [
    { "employee_id": "E-104", "work_date": "2026-06-15", "hours": 7.5, "project": "Acme" },
    { "employee_id": "E-104", "work_date": "2026-06-16", "hours": 8,   "project": "Acme" }
  ],
  "count": 2
}

Those row objects line up directly with the columns you will insert.

Step 5: Insert the rows into MySQL

Add a Connector node in Direct mode using the mysql connector and the insert-rows tool. Set table to timesheets, and map rows to the parsed array {{ parsed.items }}. The keys in each row object must match your column names, which is why the agreed CSV headers in the prerequisites matter. If your table lives in a database other than the connection default, set the optional database field. The tool inserts all rows in one call, so a CSV with twenty entries becomes twenty timesheet rows in a single step. Name the output variable inserted so you can reference the result later.

Step 6: Confirm receipt back to the sender

Because a mailhook run is async and sends no automatic reply, add a Send Email node so staff know their submission landed. Set Recipients to {{ input.replyTo }} (the sender's reply address), set the Subject to something like Timesheet received, and write a short Body referencing the count, for example We logged {{ parsed.count }} timesheet rows from {{ attachment.filename }}. Send Email uses Spojit's built-in mail service, so no connection is needed, though external recipients must be on your org allowlist under Settings -> General -> Email recipients. Save the workflow and enable it.

Step 7: Validate header-to-column mapping with Miraxa

Before going live, open Miraxa, the intelligent layer across your automation, from any page in the designer and ask it to sanity check the data path. A prompt such as "Confirm the keys produced by the csv to-json node match the columns my mysql insert-rows node writes to timesheets" lets Miraxa read your current canvas and flag a mismatch (for example a CSV header of Hours against a column named hours). If a header ever drifts, you can add a Transform node between Step 4 and Step 5 to rename keys before the insert.

Tips

  • Keep attachments within the Attachment node limits: 10 MB per attachment and 25 MB per run by default. A weekly CSV is far below this, but a year-end export might not be.
  • If staff sometimes attach several CSVs in one email, switch the Attachment node Mode to Multiple and wrap a Loop over {{ attachment.attachments }}, decoding and inserting each file in turn.
  • Set an Address prefix that staff recognise, and store the generated address somewhere central. If it leaks or gets spammed, use Regenerate address to rotate it; the old address dies instantly.
  • Use the From allowlist and Subject regex filters on the trigger to keep unrelated mail from starting runs.

Common Pitfalls

  • Header drift. The to-json keys come straight from the CSV header row, so a renamed or reordered column changes the object keys and can cause an insert error or null columns. Lock the export template, or normalise keys in a Transform node.
  • Skipping the decode step. The Attachment content field is base64, not plain text. Passing it straight into to-json produces garbage rows; always decode first as in Step 3.
  • Date and number formats. A column typed as DATE or DECIMAL in MySQL will reject values like 15/06/2026 or 7h30m. Agree on ISO dates and numeric hours, or convert them in a Transform node before the insert.
  • Duplicate submissions. Spojit deduplicates the same email message, but a staff member resending a slightly edited file is a new message and inserts again. If duplicates matter, add a unique key on the table or de-duplicate with an execute-query step.

Testing

Test on a throwaway table first. Point the insert-rows node at a timesheets_test table, send a two-row CSV to the generated mailhook address from an allowed sender, and watch the run in the execution history. Confirm the Attachment node found the file, the to-json step produced the expected count, and both rows appear in timesheets_test. Once the mapping is verified, switch the node to your live table and have one staff member submit a real timesheet before announcing the address team-wide.

Learn More

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