Process automation
Onboarding automation rebuild: 9 days down to 31 hours
Their recruiters chased the same onboarding tickets twice a week. We rebuilt the workflow around one Temporal worker. Contract-to-payroll dropped from nine days to 31 hours.

At 16:40 on a Tuesday in March, a recruiter at a 17-person staffing agency in Den Bosch signs a warehouse picker. The candidate is keen to start Thursday morning. The recruiter knows that is not going to happen. Even if every system cooperates, the first payroll run is nine days away.
She does not tell the candidate that. She tells him she will be in touch by Friday with paperwork, then opens a Trello board, finds the column titled "New candidates", and adds a card. The card has a 23-item checklist underneath. She ticks the first three items and goes home.
That was the workflow we were asked to rewrite.
What the 23 steps actually were
When you ask a staffing agency how onboarding works, you usually get a sentence with the word "just" in it. "We just need to get them in the system, then payroll picks them up." Then you sit in their office for a day and the number of "just"s collapses.
The real list, as we wrote it down on the whiteboard that first Tuesday, was three humans and twenty systems. ID scan and verification. VOG application. Bank account capture. Loonheffingenkorting form. Pension scheme enrolment. Health-and-safety briefing PDF and sign-off. Welcome email. Time-tracking account. Payroll account in Loket. Client-portal account. ABU compliance check. Sector-specific onboarding for warehouse, kitchen, or care roles. Sick-leave insurance registration. UWV declarations. First-shift scheduling. Confirmation back to the recruiter. And five more bits that only the office manager remembered.
None of the systems talked to each other. Every integration was a one-way email or a CSV upload that someone did at 17:00 once a day.
Where the nine days actually went
When we mapped the timeline against the calendar, the long tail came from four places.
First, the VOG. The Verklaring Omtrent het Gedrag is the Dutch certificate of good conduct, issued by the Ministry of Justice. Justis publishes a target window of a few business days for digital requests through eHerkenning, and several weeks for paper. The agency was submitting on paper, because that is how the office manager had always done it.
Second, the bank verification. The recruiter typed the candidate's IBAN into a spreadsheet that got uploaded to the payroll system at midnight. If the IBAN had a typo, nobody knew until the next morning, when it bounced.
Third, the payroll cutoff. Loket's weekly run cuts off at 14:00 on Wednesday. If your employee was finalised at 14:01, they waited a full week.
Fourth, the Trello board. The board was the source of truth, which meant that any step that was not ticked was either done and untracked, or not done. There was no way to tell which.
Picking Temporal over a queue and a database table
The instinct, when you see this kind of process, is to reach for a job queue. Sidekiq, Bull, SQS. Build a state machine in your application, push jobs onto a queue, store the state in Postgres, and accept that you will spend the next year writing the retry logic.
That pattern works, until the workflow has to survive a deploy halfway through. Or a human takes two days to approve something. Or one of the twenty integrations returns a 502 and you need to retry it with exponential backoff while preserving the rest of the workflow's state. Each of those is a weekend of engineering. Stacked, they are a year.
For this rewrite we picked Temporal. Temporal is a workflow engine that treats the long-running process as the unit of code. You write a workflow function that reads synchronously. The engine handles persistence, retries, timers, and signals. If your process has to wait three days for a VOG to come back, the workflow function just awaits the result. The worker can die, the cluster can be redeployed, and when the workflow resumes it picks up exactly where it left off.
That trade pays back when the process is long-running, involves humans, and touches more than three external systems. All three applied here.
The worker that replaced the Trello board
The entire onboarding process now lives in one workflow function. It is shorter than the original Trello checklist.
import {
proxyActivities,
defineSignal,
setHandler,
condition,
} from '@temporalio/workflow';
import type * as activities from './activities';
const {
requestVOG,
pollVOGStatus,
createPayrollEmployee,
createTimeTrackingAccount,
verifyBankAccount,
sendOnboardingPacket,
scheduleFirstShift,
enqueueForPayrollRun,
} = proxyActivities<typeof activities>({
startToCloseTimeout: '5 minutes',
retry: { maximumAttempts: 5 },
});
export const idDocumentApproved = defineSignal<[boolean]>('idDocumentApproved');
export async function onboardCandidate(contract: SignedContract): Promise<void> {
// Kick off the things that can run without a human, in parallel.
const [vogRef, payrollId] = await Promise.all([
requestVOG(contract.candidate),
createPayrollEmployee(contract),
]);
await verifyBankAccount(contract.candidate.iban);
await createTimeTrackingAccount(payrollId, contract);
// Wait for the recruiter to approve the ID scan, with a 48h timeout.
let approved = false;
setHandler(idDocumentApproved, (ok) => { approved = ok; });
await condition(() => approved, '48 hours');
// The VOG can take days. The activity polls patiently with backoff.
await pollVOGStatus(vogRef);
await sendOnboardingPacket(contract);
await scheduleFirstShift(payrollId, contract.startDate);
await enqueueForPayrollRun(payrollId, contract.payPeriod);
}
A few things worth pointing at.
The function reads top-to-bottom like a description of the process. No event bus, no state-machine diagram pinned to a wall, no "what happens if the worker dies between step 14 and step 15." Temporal owns that.
requestVOG and createPayrollEmployee run in parallel because they do not depend on each other. The recruiter does not see the parallelism. She just sees that both finished. It shaves an hour off the median wall-clock.
The idDocumentApproved signal is the human-in-the-loop bit. The recruiter reviews the scanned ID in our internal dashboard and clicks Approve. The dashboard fires the signal. The workflow advances. If she does not click within 48 hours, the workflow fails out into a recovery queue that someone phones.
Activities are idempotent by construction. Here is the payroll-employee one:
export async function createPayrollEmployee(
contract: SignedContract,
): Promise<string> {
// Loket's API blows up on duplicates. We use a deterministic external id
// built from BSN plus signed-at timestamp, so retries hit the same row.
const externalId = `cand-${contract.candidate.bsn}-${contract.signedAt}`;
const existing = await loket.employees.findByExternalId(externalId);
if (existing) return existing.id;
const created = await loket.employees.create({
externalId,
bsn: contract.candidate.bsn,
firstName: contract.candidate.firstName,
lastName: contract.candidate.lastName,
iban: contract.candidate.iban,
contract: mapContract(contract),
});
return created.id;
}
If your activity does something the outside world remembers (creates a user, sends an email, schedules a payment), make it idempotent. Temporal will retry on your behalf. It does not know that "send onboarding email" is unsafe to call twice.
Wrapping the twenty systems behind one interface
The activity functions look simple from inside the workflow. The implementations are not. Each of the twenty external systems has its own personality. Loket exposes a sensible REST API with predictable error codes. The VOG portal does not have a real API at all; we drive eHerkenning through a headless browser and a service account. The pension provider sends back a confirmation PDF by email, which a small inbox-watcher parses for the policy number. The time-tracking vendor's API requires a session cookie that expires every six hours, so the adapter holds a small login loop next to the call.
We pushed all of that mess into the activity layer, behind a thin adapter for each system. The workflow does not know that pollVOGStatus involves a Puppeteer session, or that createPayrollEmployee retries through three error codes Loket only documents in a 2019 PDF. It sees functions that return values. When the VOG portal goes down for maintenance, we rewrite the adapter and the workflow does not change. That separation is the second reason Temporal pays back: the workflow is the spec, the activities are the dirt.
The Wednesday-at-14:00 cutoff, solved with a timer
The payroll cutoff used to be the thing that turned a fast onboarding into a slow one. We could finish everything by Wednesday morning and still miss the run because the bank-verification batch had not completed.
The Temporal workflow handles cutoffs explicitly. After every prerequisite is done, enqueueForPayrollRun calculates the next valid payroll moment from the pay period and the cutoff calendar, then sleeps inside the workflow until that moment before doing the actual enqueue.
If the worker is redeployed during the sleep, the timer survives the redeploy. If the workflow finishes at 13:55 on a Wednesday, the enqueue fires at 13:58. If it finishes at 14:02, it waits until next Wednesday. The recruiter sees that in the dashboard the moment the timer is set, so she can warn the candidate before he buys a bus pass.
What changed for the team
Two months after we shipped, the median contract-to-payroll time was 31 hours. The p90 was 56 hours. The p95 was bounded almost entirely by VOG wait time, which is the Ministry of Justice's job to fix, not ours.
The recruiters did not get fired. There was a hot take on the Hacker News front page last week, the one about CEOs who think AI replaces their employees being mostly bad CEOs, and the Den Bosch agency's owner is not one of those CEOs. He looked at the freed hours and asked his team what they wanted to do with them.
What they wanted to do was call more candidates. They closed roughly 30% more contracts in the quarter after we shipped, with the same headcount and without changing the marketing budget. The Trello board still exists. It is empty.
The point of automating a 23-step process is not to delete the people who used to run it. It is to give them back the hours they were spending on the steps, so they can do the work the steps were getting in the way of.
The new shape of the recruiter's day
The first thing we wanted to know after we shipped was what the work looked like from the recruiter's chair, not from Grafana. We sat with the recruiter from the opening anecdote four months in. Her morning used to start with the Trello board: opening it, scrolling for cards she had ticked the day before but forgotten to chase, and writing herself a sticky note for the three or four things she was sure she had missed somewhere.
Now her morning starts with the stuck list. It is the list of workflows waiting on her: an ID scan to approve, a contract change to confirm, a candidate who has not returned a signed letter. The list is usually empty by 09:30. She has not opened the Trello board in two months. The team also stopped doing the Friday-afternoon Trello cleanup, which used to be ninety minutes of three people arguing over whether a checkbox should be ticked. We did not budget that ninety minutes in the original ROI, and it was the line item the office manager mentioned first when we asked her what was different.
What we would do differently next time
Two things, mostly.
We over-built the dashboard. The first version had a full Temporal-style workflow viewer for the recruiters: visible state for every activity, retry counters, timer countdowns. They did not want it. They wanted a single list with "what is stuck and who do I call." We trimmed the dashboard by about 60% in the second sprint and nobody complained.
We under-modelled the contract changes. Roughly one in eight candidates changes something between signing and starting: hours, hourly rate, sometimes the start date. The first version treated those as a brand-new onboarding, which double-booked them in Loket. The current version uses Temporal's update functionality to mutate a running workflow in place, which is the cleaner pattern but took us two weeks of staring at it to get right.
The five-minute audit you can run on your own process
When we built the onboarding worker for the Den Bosch agency, the thing we kept running into was that "the process" was not written down anywhere. It was twenty-three habits in three people's heads. We ended up spending a week in their office before writing a single line of Temporal code, just watching. If you are thinking about process automation for your own operations, that observation week is the cheapest week of the project.
Before you write any code, do this. Pick one process in your business that has a queue in front of it: candidate onboarding, invoice approval, refund processing, supplier vetting. Time it five times this week with a stopwatch. The gap between your fastest run and your slowest run is exactly where the work is.
Key takeaway
Long-running processes survive better as workflow functions than as state machines stitched together with job queues and database rows.
FAQ
Why use Temporal instead of a job queue like Sidekiq or Bull?
Job queues handle one task at a time. Temporal handles the whole long-running process as one function. The trade pays back when work spans days, waits on humans, and touches several external systems.
How do you keep activities idempotent when an external system charges per call?
Build a deterministic external ID per workflow run and check before creating. Most Dutch payroll vendors expose findByExternalId. Temporal retries on your behalf, so the first write has to be safe to repeat.
What is the smallest process worth automating with Temporal?
If a process spans more than a day, involves at least one human approval, and touches three or more external systems, Temporal pays back inside a quarter. Below that, a queue and a Postgres table are usually fine.
What is the VOG and why does it slow Dutch onboarding down?
The VOG is the Dutch certificate of good conduct, issued by the Ministry of Justice. Digital eHerkenning requests take a few business days; paper applications take weeks. Most onboarding delays trace back to it.