- Amogh's Hot Takes
- Posts
- A software engineer builds a registration system with no-code tools
A software engineer builds a registration system with no-code tools
And succeeds in building it but fails in other ways
I’ve been a member of the New York Choir Project for a few years. It’s a choir organization where we sing and perform pop songs. There are about 250+ people between 5 choirs.
I asked Charlie the choir director how she manages all the logistical aspects of the choir. The answer didn’t surprise me: spreadsheets on spreadsheets. She hasn’t found a singular class registration software offering that meets her specific needs. Her biggest headache comes from manually adding and removing people from the choir waitlist. It’s difficult to keep track of and takes a ton of her time to email everyone on the waitlist repeatedly.
My recommendation to her was to build a custom registration with low-code tools, as software is always more expensive to maintain than it is to build. With any custom software solution, you are always beholden to a software engineer to maintain it. With a low-code solution, maintenance is self-serve.
So with an abundance of time in my funemployment, I volunteered to build it!
How it works

People who want to join the choir waitlist fill out a form, this adds them to the waitlist table. Once there is an open spot in one of the choirs, Charlie manually emails them giving them a payment link.
After payment, they are added to a choir membership table. The waitlist table and each choir table are synced to email lists. When added to the choir members email list, they are removed from the waitlist table and email list.
Should someone decide they don’t want to be on the waitlist anymore, they can unsubscribe from the waitlist email list; this removes them from the table too.
There is also an automation to create a choir table, payment links, and email lists.

The Stack
Automation / Orchestration
The general consensus is that Zapier is the easiest to use automation platform and supports more integrations than other competitors. I found this to be true as I started creating Zaps (automations) within minutes. But an easy-to-use platform is a double-edged sword; it is difficult to customize.
I tried to create an automation that would dynamically query a table in Airtable based on a search input and then write data to it. I was able to query the table, but Zapier would not let me write any data to it because it didn’t know the table’s schema in advance.
Understandably, Zapier is not confident in my ability to match the schema without its hand-holding. It wants to ensure my automations have the highest chance of succeeding and not error out. It’s likely that another automation solution with more advanced features could address this.
Database
The two free tier user-friendly database options I know of are Airtable and Google Sheets. Having used Airtable a few times, I really enjoyed the experience and UX. Plus I really wanted to move Charlie away from spreadsheets. Airtable proved to be my favorite of the software chosen for this project.
Payments
As a former employee of Square, one would surely think I would chose this software for payments? Sorry to disappoint my readers—many of you who currently work at Square—I ended up going with Stripe. Please forgive me.
But my rationale is simple. I know my way around the Square platform and it’s better suited to serve specific segments of customers. Ecommerce, restaurants, retail? It has every feature you need.
But my primary goal was to choose the simplest solution for Charlie. I needed the bare minimum of functionality; in that way Stripe is better. Imagine it like going to a hardware store and only buying a hammer, nails, and level. Square offers you a full toolbox of distracting extras.
The one key feature needed for the choir registration system that Stripe lacks is inventory management; each choir has limited spots. Stripe, being an API company first, assumes the application you are building will handle inventory management. Stripe payment links do let you limit the number of times a payment link can be used however, so this works well enough to limit choir sign-ups.
In contrast, this would have been very easy to manage with Square’s inventory feature. But I am still scarred from using Square’s Catalog API when working on Square for Restaurants. I can’t finish this project if I am blocked by past trauma.
I looked into Mailchimp and Constant Contact as two established marketing email players with many actions and triggers available in Zapier. Ultimately, I was able to solve problem of the organizing members in the choir by using Mailchimp contact tags; this came in at a significantly cheaper subscription tier.
Building with no-code is just like building with code
Just like in code, keeping my automations modular made them easier to work with. I originally had a large automation:
Trigger - Take payment
Add to choir member table
Add to choir member email list
Remove from waitlist email table
Remove from waitlist email list
But I needed to duplicate this automation because it needed to work for each separate choir member table. To minimize code duplication and drift, I split the automation like so. Now only the second automation has to be duplicated.
Trigger - Take payment
Find customer info from payment
Insert into appropriate table
Trigger - new choir member added to table
Add member to current members choir email list
Trigger - member added to current members choir email list
Remove from waitlist email list
Remove from waitlist table
An engineering design doc for this project is likely overkill, but some more upfront planning would have been helpful. Now that I fully understand the capabilities and limitations of Zapier and each integration, I would create a light eng design for a future project.
I wish there was a way to write unit tests for each automation. Zapier doesn’t let you ship an automation without testing it first which is better than nothing. I’ll just have to live with some background anxiety about my automations breaking unexpectedly. At least there is no PagerDuty involved.
Well, no PagerDuty integration that I have set up anyway.
Zapier’s Copilot
Zapier’s catalog for built in triggers and actions is extensive, but not exhaustive. For this, Zapier’s Copilot fills in the blanks. It’s probably one of the best code generation tools I’ve used. You prompt it with what you want, it’ll reference API docs for each platform, and then produce working integration code. The code sometimes makes unneeded API calls or writes unused code, but I am impressed with how high it’s success rate is.
The one thing it doesn’t do well is precise debugging. With ChatGPT or Cursor, you can ask the AI questions. Zapier Copilot prefers to take action, it is not that interested in your debugging suggestions.
This makes sense though. I imagine the average Zapier user wouldn’t know where to start debugging. The only input they can provide is “it’s not working.” It’s better for the AI to be biased towards debugging independently.
AI hasn’t replaced me yet
The first significant issue I encountered was fetching customer data given a Stripe payment. Not all data in Stripe API responses are consistent. This is a reasonable given all the payment methods Stripe supports. But Zapier just couldn't handle it. If Zapier encounters a null field, it preemptively throws an error. Nevermind that the Airtable API can receive null data without issue.
So I had to dust off my keyboard and write some simple name || 'unknown'
code myself. While I was at it, I decided to split the name into first and last. Zapier has some utility blocks to handle simple operations like string transformation, but it was so much easier to just write 5 lines of code to address this.
const sessionData = await fetchWithZapier(url + sessionId);
const email = sessionData.customer_details?.email || null;
const name = sessionData.customer_details?.name || null;
let firstName = 'unknown';
let lastName = firstName;
if (name && name.includes(' ')) {
const [ extractedFirstName, extractedLastName ] = name.split(' ');
if (extractedFirstName) {
firstName = extractedFirstName
}
if (extractedLastName) {
lastName = extractedLastName
}
}
return {
email,
firstName,
lastName,
}
And the earlier Zapier issue about the unknown schema? I solved that by hardcoding my schema into my code, knowing it is correct. If Zapier's AI were to gain sentience, it would scream in horror at this “unsafe” code.
fetch(`/v1/write/${tableName}`, {
body: {
Email: email,
"First Name": firstName,
"Last Name": lastName,
}
)
Conclusions
I was happy that I had completed the registration system, but I had failed in my goal of building with no code.
Zapier, and no code tools in general, are remarkable platforms. Even just the benefits of abstracting away hosting and authentication are tremendous time savers. For future software projects, I might opt to build serverless functions with Zapier.
Building a full registration application from scratch to combine all these tools would have likely taken me 2-4 weeks. Instead, I finished this project in 20-30 hours.
As I hand off this system to Charlie, I do worry about what happens if the custom actions break. I'm confident it will continue to work smoothly, but there there will always be some reliance on me or someone who can write code.
We haven’t quite reached the dream of building software entirely with no-code solutions. Some knowledge of code and how to read API documentation is necessary. My key takeaway is that customizability and ease of use are always a tradeoff. You can't have them both. The robots aren’t taking my job quite yet.
Off Topic
In choosing software for this project, I ran into limiting usage at the free or lower cost subscription levels. Some examples: limited number of user accounts per organization and limiting the number of email lists. We weren’t using any other advanced features of the app and couldn’t justify the cost of a higher tier.
I once heard this hot take against charging by the number of accounts for freemium software. The case was to let customers explore the product as much as they wanted to encourage product adoption. You should instead charge by how much customers are using it. From my experience, I’d have to agree.
Reply