ag
← back
June 18, 2025Case studyPost-sales

Bridging a roadmap gap: 173 anonymous form links for a client, in minutes

A client needed 173 anonymous form links; the platform made them one at a time and the bulk feature was still on the roadmap. A short script over the platform's own GraphQL API closed the gap the same day - and Customer Success reused it for several more clients.

Outcome

Kept a client unblocked while the real feature was built, and gave Customer Success a tool they reused for several more clients.

TypeScriptAutomationGraphQLCSVCustomer Success

Client work on a compliance platform. Client details are kept confidential; what follows is the problem, the approach, and the result.

The problem

Customer Success came to me with an urgent request. A client needed 173 anonymous form links - one per respondent - and needed them now. The platform could create an anonymous link, but only one at a time, through the UI:

  1. Start a new workflow.
  2. Wait for it to initialize.
  3. Find the anonymous link in the workflow metadata.
  4. Copy it somewhere safe.
  5. Repeat 172 more times.

A bulk feature was on the roadmap. The client couldn't wait for it, and asking CSMs to spend hours clicking was not a real answer. The useful question was narrower: what does the UI actually do, and can I call that directly?

The design

The UI was a thin layer over the platform's GraphQL API. Starting a workflow was one mutation; the anonymous link lived in the metadata of one of the resulting tasks. So the script did what a person would do, in a loop:

for (let i = 0; i < numberOfWorkflows; i++) {
  const workflow = await startNewWorkflow(
    tenant.id,
    targetWorkflow?.workflowCollectionId,
    targetWorkflow?.tenantTeamId,
  );

  const workflowProcess = await getWorkflowProcess({
    workflowId: workflow.data.startWorkflowProcess.id,
    tenantId: tenant.id,
  });

  const linkTask = workflowProcess.allTasks.find((task) =>
    task.metadata.includes("anonymousLink"),
  );

  if (linkTask) {
    const { anonymousLink } = JSON.parse(linkTask.metadata);
    fs.appendFileSync(csvPath, `${i + 1},${anonymousLink}\n`);
  }

  await new Promise((resolve) => setTimeout(resolve, 1000));
}

Three judgment calls:

  1. Use the platform's own API, not a new feature. Building the bulk feature properly was product work with its own priority. The script reused existing, supported calls, so it carried no new risk to the platform and didn't jump the roadmap queue.
  2. Go slow on purpose. A one-second delay between workflows meant the run took minutes instead of seconds. That was a good trade against load on a production system shared with every other customer.
  3. Make it reusable by changing three inputs. Organization, workflow name, and count are the only things that differ between clients. Each run writes a timestamped CSV, so reruns never overwrite earlier output.

Results

  • 173 links delivered in about 5 minutes, against hours of manual clicking.
  • Reused for several more clients. Customer Success came back for other accounts with the same need; each run was a change of three inputs.
  • Bought time for the product team. The client was unblocked while the proper bulk feature was built. Once it shipped, the script was retired.

The principle

When a customer is blocked by a gap on the roadmap, first find out what the product already does underneath its UI. The fastest safe fix is often the platform's own API, driven carefully - it unblocks the account today and leaves the real feature to be built properly.