Bulk link generator: platform extension
Context
Sometimes the most satisfying solutions come from working around limitations rather than waiting for them to be solved. When our Customer Success team came to me with an urgent request, the situation was clear: A client needed to generate hundreds of anonymous form links, but our platform only supported creating these one at a time (via our UI). The proper feature was on the roadmap, but the client needed a solution now.
Note: While specific client details are kept confidential, this case study shows how creative automation can bridge the gap between immediate customer needs and planned feature development.
The Challenge
Picture this: You have a platform feature that lets you create a single anonymous form link. Great! But what if you need 173 of them? Not so great. The manual process would involve:
- Start a new workflow
- Wait for it to initialize
- Find the anonymous link in the metadata
- Copy it somewhere safe
- Repeat... 172 more times 😱
Our CSM team's collective response: "There has to be a better way!"
What I Built
I developed what I like to call the "Link-O-Matic 3000" (okay, it's just a TypeScript script, but let's have some fun with it). The script could:
- Authenticate with our platform
- Create workflows in rapid succession
- Extract anonymous links from workflow metadata
- Generate a nicely formatted CSV file
- Do it all without human intervention
The Secret Sauce
The magic happened in understanding the workflow creation process. Here's the core logic:
const organization = "qhseportal";
const workflowName = "UK Health & Safety Review";
const numberOfWorkflows = 173; // The magic number!
// Create workflows and extract links
for (let i = 0; i < numberOfWorkflows; i++) {
console.log(`Creating workflow ${i + 1} of ${numberOfWorkflows}`);
const workflow = await startNewWorkflow(
tenant.id,
concerendWorkflow?.workflowCollectionId,
concerendWorkflow?.tenantTeamId,
);
const workflowProcess = await getWorkflowProcess({
workflowId: workflow.data.startWorkflowProcess.id,
tenantId: tenant.id,
});
// Hunt for that precious anonymous link in the metadata
const anonymousLinkAvailable = workflowProcess.allTasks.find((task) =>
task.metadata.includes("anonymousLink"),
);
if (anonymousLinkAvailable) {
const anonymousLinkMetadata = JSON.parse(anonymousLinkAvailable.metadata);
const anonymousLink = anonymousLinkMetadata.anonymousLink;
// Save it for posterity (and the client)
const csvContent = `${i + 1},${anonymousLink}\n`;
fs.appendFileSync(csvPath, csvContent);
}
// Be nice to our API
await new Promise((resolve) => setTimeout(resolve, 1000));
}
Thoughtful Details
- Rate Limiting: Added a 1-second delay between requests because being a good API citizen is important!
await new Promise((resolve) => setTimeout(resolve, 1000));
- Real-time Progress: Because watching numbers go up is satisfying:
console.log(`Creating workflow ${i + 1} of ${numberOfWorkflows}`);
- Timestamp-based Files: Each run creates a unique CSV file:
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const csvPath = path.join(__dirname, `anonymousLinks-${timestamp}.csv`);
The Results
The script turned what would have been hours of mind-numbing clicking into a 5-minute automated process. The output? A beautiful CSV file with rows of anonymous links, ready to be handed to the client:
Row,AnonymousLink
1,https://capptions.direct/.../anonymous-task/c2d7556c-...
2,https://capptions.direct/.../anonymous-task/1103443e-...
...
173,https://capptions.direct/.../anonymous-task/a5dc22ca-...
What I Learned
-
The Power of Workarounds: Sometimes the best solution isn't waiting for the "proper" feature but finding creative ways to use what you have.
-
API Understanding is Crucial: By understanding our platform's GraphQL API flow, I could automate what was designed to be a manual process.
-
Customer Success Partnership: Working closely with CSM team led to a solution that:
- Saved them time
- Made customers happy
- Bought development time for the proper feature
-
Rate Limiting is Your Friend: Just because you can hammer an API doesn't mean you should. A small delay between requests makes everyone happier... and avoid network related issues (ew!).
Impact & Reusability
The script became something of a hit with our CSM team. They came back multiple times for different clients needing bulk anonymous links. Each time, it was just a matter of:
- Update the organization and workflow name
- Set the number of links needed
- Run and wait for the CSV
It was a perfect example of how a small automation script can have an outsized impact on customer satisfaction and team efficiency.
What's Next?
While this script served its purpose beautifully (and got multiple encores!), it was always meant to be a temporary solution. The proper feature has since been built into the platform, making this script a fond memory of creative problem-solving.
But the lesson lives on: Sometimes the best solutions come not from building the perfect feature, but from creatively using what you have to solve immediate problems.
Key Takeaways
Well, a little realisation was that being a good developer isn't just about writing the best or most efficient code - it's about solving problems. Sometimes that means building new features, and sometimes it means finding clever ways to make existing features do new tricks. ...or just automating the heck out of your platform with its current GQL APIs. 😉
The script might have been temporary, but it:
- Solved an immediate customer need
- Gave the development team breathing room
- Provided valuable insights for the eventual feature
- Made our CSM team's life easier
And really, isn't that what good automation is all about?
Note: This case study focuses on the technical implementation while respecting confidentiality around specific client details. The script has since been retired in favor of proper platform features, but its legacy lives on in happy customers and satisfied CSMs.