How to Onboard New Hires from Emailed HR Forms with a Mailhook
Forward a new-hire form email to a Spojit mailhook, extract the candidate's details from the attached PDF, and create a Deputy employee once a manager approves.
What This Integration Does
Many HR teams still receive new-hire paperwork as a PDF emailed by a recruiter, a hiring manager, or a candidate. Re-keying names, contact details, and start dates into Deputy by hand is slow and error-prone. This Spojit workflow turns that inbox step into an automation: HR forwards (or BCCs) the new-hire form to a dedicated mailhook address, Spojit reads the PDF, pulls the relevant fields, pauses for a manager to confirm, and then creates the employee in Deputy. A Slack message keeps the HR channel informed at the end.
The workflow is triggered by a Mailhook trigger, so each email that lands at the generated address starts one run within seconds. The PDF bytes are fetched with an Attachment node, a Connector node in Agent mode extracts the fields into structured JSON, and a Human node holds the run until a manager approves. Nothing is written to Deputy before approval, so a rejected or timed-out request leaves no record behind. Runs are deduplicated per message, and each forwarded email produces its own independent execution you can inspect in the run history.
Prerequisites
- A Deputy connection with permission to create employees (the
create-employeetool writes to the Employee resource). - A Slack connection authorized to post to your HR channel, and the channel ID or name you want to notify.
- The pdf utility connector (built in, no authentication) for reading text from the attached form.
- At least one approver set up: a User, Role, or Team you can assign to the Human node's approval slot. See Using Human Approval Nodes.
- A consistent new-hire form layout. The cleaner and more predictable the PDF, the more reliable the extraction.
Step 1: Add a Mailhook trigger and generate the address
Create a new workflow and set the Trigger node type to Mailhook. Optionally set an Address prefix (1-24 characters, default mh) such as new-hire, then click Generate email address. Spojit produces a unique address in the form new-hire-<random16>@mailhook.spojit.com. Copy it and give it to HR as the forwarding target, or add it as a BCC on the recruiter's submission template.
To keep stray mail out, set an optional From allowlist (for example your HR and recruiting domains) and a Subject regex like (?i)new hire. The mailhook fires whether the address is in To, Cc, or Bcc. Each run exposes the email as {{ input }}, including {{ input.subject }}, {{ input.from }}, {{ input.replyTo }}, and {{ input.attachments }}.
Step 2: Fetch the PDF with an Attachment node
Add an Attachment node directly after the trigger. The designer only saves this node in a Mailhook workflow, which is exactly what you have. Configure it to grab the form:
- Mode:
Singleso you get one attachment back as an object. - Content type:
application/pdf. - Filename pattern: a glob such as
*.pdf(ornew-hire-*.pdfif your form has a stable name). - Fail if no attachment matches: turn this on so an email without a form stops the run instead of creating an empty employee.
The node outputs { filename, contentType, size, content }, where content is the base64-encoded PDF. Name the output something like form so you can reference {{ form.content }} downstream. Attachments are limited to 10 MB each and 25 MB per run by default.
Step 3: Read the form text with the pdf connector
Add a Connector node in Direct mode and pick the pdf connector with the extract-text tool. Map its document input to the base64 attachment from the previous step, {{ form.content }}. Direct mode is deterministic and costs no AI credits, which makes it the right choice for a single predictable call. Name the output formText; the extracted plain text becomes available as {{ formText.text }} for the extraction step.
If your forms are scanned images rather than digital PDFs, extract-text will return little or no text. In that case, embed the document into a Knowledge node in Embed mode with Document Type set to Images via OCR, then query it in the same run using a Transient collection. For standard digital PDFs, extract-text is faster and cheaper.
Step 4: Extract the fields with a Connector node in Agent mode
Add a Connector node and switch it to Agent mode. Agent mode lets Miraxa, the intelligent layer across your automation, read the messy form text and return clean fields. Define a Response Schema so the output is reliable JSON rather than prose:
{
"type": "object",
"properties": {
"firstName": { "type": "string" },
"lastName": { "type": "string" },
"email": { "type": "string" },
"mobile": { "type": "string" },
"startDate": { "type": "string" },
"position": { "type": "string" },
"location": { "type": "string" }
},
"required": ["firstName", "lastName", "email"]
}
Use a prompt such as: Extract the new hire details from this form text and return them in the response schema. Form text: {{ formText.text }}. Name the output hire, giving you {{ hire.firstName }}, {{ hire.email }}, {{ hire.startDate }}, and the rest for later steps.
Step 5: Pause for manager approval with a Human node
Add a Human node so no employee is created without sign-off. Configure it so the approver sees exactly who is about to be added:
- Label:
Approve new hire. - Message:
Create Deputy employee for {{ hire.firstName }} {{ hire.lastName }} ({{ hire.email }}), starting {{ hire.startDate }}? - Approval slots: add the hiring manager as a User atom, or a Role/Team atom so any member can approve. Approval completes only when every slot is satisfied.
- Timeout (minutes): set a window such as
2880(two days) if you want stale requests to auto-close. A timeout is treated as a rejection. - Email approvers: turn on if your managers do not watch the in-app Approvals inbox.
On approval the node outputs { approved: true, approvalId, outcome: "APPROVED" } and the run continues. A rejection or timeout halts the workflow, so nothing is written to Deputy. Branching on rejection is not supported, which is the safe default here.
Step 6: Create the employee in Deputy
Add a Connector node in Direct mode, choose the Deputy connector, and select the create-employee tool. Build the employee object from the extracted fields. Deputy expects capitalized field names:
{
"FirstName": "{{ hire.firstName }}",
"LastName": "{{ hire.lastName }}",
"Email": "{{ hire.email }}",
"Mobile": "{{ hire.mobile }}"
}
Name the output employee. Use the response (for example the new employee Id) in the next step's confirmation message. Keep this in Direct mode: it is a single, deterministic write that should never be left to a model's discretion.
Step 7: Notify HR in Slack
Finish with a Connector node in Direct mode using the Slack connector and the send-message tool. Set the channel to your HR channel and the text to a clear confirmation, for example: New hire created in Deputy: {{ hire.firstName }} {{ hire.lastName }} ({{ hire.email }}), starting {{ hire.startDate }}. If you would rather acknowledge the submitter, add a Send Email node addressed to {{ input.replyTo }} instead of or alongside the Slack post, since a mailhook never replies to the sender on its own.
Tips
- Scaffold the whole flow fast by asking Miraxa:
Build a workflow that watches a mailhook, fetches the PDF attachment, extracts new-hire fields with an Agent mode node, requires manager approval, then creates a Deputy employee and posts to Slack.Then fine-tune each node in the properties panel. - Use the Subject regex and From allowlist on the trigger to reject newsletters and replies before they ever reach the extraction step, saving AI credits.
- Make the Response Schema fields you actually rely on
required, so a form missing an email surfaces as an extraction failure rather than a half-built Deputy record. - If forms sometimes carry multiple attachments (form plus ID scan), set the Attachment node Filename pattern narrowly so it matches only the new-hire form.
Common Pitfalls
- Saving an Attachment node in a workflow whose trigger is not a Mailhook: the designer refuses it. The trigger must be Mailhook.
- Scanned (image-only) PDFs return empty text from
extract-text. Switch to a Knowledge node with Images via OCR for those, or ask HR to send digital PDFs. - Wrong Deputy field casing. The
create-employeetool expectsFirstName,LastName, andEmail(capitalized), not the lowercase keys from your extraction schema. - Assuming a rejected approval can branch to a fallback. A rejected or timed-out Human node halts the run, so put nothing critical after it that should also handle the reject case.
- Rotating the mailhook with Regenerate address kills the old address instantly. Update HR's forwarding rule before regenerating.
Testing
Before pointing real HR mail at the address, forward one sample new-hire form to the generated mailhook address yourself. Watch the run appear in the execution history within seconds, then confirm the Attachment node returned the PDF, the extract-text output has readable text, and the Agent mode node produced the expected {{ hire.* }} fields. Approve the request from the Approvals inbox and verify the employee shows up in Deputy and the Slack message posts. Once a clean form passes end to end, test a deliberately bad email (no attachment) to confirm Fail if no attachment matches stops the run cleanly. Then share the address with HR.