When I started building LoopSignal, I did not begin with GitHub sync, changelogs, or billing.
I started with the very first thing a user would do: submit feedback.
That sounds small, but for a product like LoopSignal, it is the foundation for everything else. If submitting feedback feels annoying, too formal, or too slow, the rest of the product does not matter. No votes, no roadmap, no changelog, no loop to close.
So this first article in the series is about the first core problem I wanted to solve:
How do you let users submit useful product feedback with as little friction as possible, without opening the door to spam and chaos?
The problem with most feedback forms
A lot of feedback tools make the same mistake early.
They treat feedback submission like a mini onboarding flow.
Create an account.
Verify your email.
Fill out a long form.
Pick a category.
Pick a board.
Confirm your submission.
At that point, most users are already gone.
That might work if your users are deeply invested power users, but for most SaaS products, feedback happens in small moments:
- someone hits a rough edge
- someone wants one missing feature
- someone notices a bug
- someone has an idea while using the app
If you add too much friction in that moment, you lose the feedback entirely.
For LoopSignal, I wanted the opposite approach:
- no account required
- optional email for updates
- short, low-friction form
- moderation before public visibility
- enough checks to prevent obvious abuse
That became the starting point.
What the submission flow needed to do
Before I wrote any UI, I made a short list of requirements.
A submission should:
- work from a public board
- work from an embedded widget
- allow anonymous users
- optionally collect an email for status updates
- validate title and description
- rate-limit abusive traffic
- detect obvious spam
- store the post in a pending state for moderation
- subscribe the submitter for future notifications
That is not a huge system, but it is enough to turn a simple form into a real product feature.
Why anonymous submission mattered
This was one of the earliest product decisions I felt strongly about.
If LoopSignal is meant for indie developers and small teams, it cannot assume their end users want to create accounts just to suggest something.
A user should be able to open a public board, type:
"Would love dark mode scheduling"
and hit submit.
That is it.
If they want updates, they can leave an email. If they do not, the feedback should still count.
That one decision shapes a lot of the implementation:
- you cannot rely on user auth for identity
- you need basic anti-spam protections
- you need moderation
- you need a clean public workflow for anonymous posts
But I still think it is the right tradeoff. More friction might make the system cleaner internally, but it would also make the product worse at collecting signal.
The data model
At the database level, the first important table was posts.
I needed each feedback item to track both moderation state and product workflow state, because those are different things.
A post might be:
- pending moderation
- approved and open
- planned
- in progress
- completed
- closed
- rejected before ever becoming public
That led to fields like these:
type Post = {
id: string;
project_id: string;
title: string;
description: string | null;
category: string | null;
email: string | null;
ip: string | null;
spam_score: number;
moderation_status: "pending" | "approved" | "rejected";
workflow_status: "open" | "planned" | "in_progress" | "completed" | "closed";
created_at: string;
};
The important part here is that moderation and workflow are separate.
That keeps the public board clean and makes the later product flow much easier to reason about.
Why I used a server action for submission
LoopSignal is built with Next.js 15, and for this kind of mutation, server actions felt like a good fit.
I did not need a big REST surface just to support one form. I needed a secure write path with access to:
- request metadata
- auth context if available
- headers and IP information
- database access
- cache revalidation
That is exactly the kind of job server actions are good at.
The flow looked like this:
- Validate the incoming form data
- Read the client IP
- Check rate limits
- Run spam scoring
- Insert the post as
pending - Subscribe the email if one exists
- Return a simple success or error response
The core of it looked like this:
const spam = checkSpam({ title, description, email, ip });
if (spam.isSpam) {
await logSpam(ip, "spam_detected", spam.signals, spam.score, {
title,
description,
email,
});
return { error: "Your submission was flagged." };
}
const { data: newPost } = await admin
.from("posts")
.insert({
project_id: projectId,
title,
description,
category,
email: email || user?.email || null,
ip,
spam_score: spam.score,
moderation_status: "pending",
workflow_status: "open",
})
.select("id")
.single();
There is nothing especially fancy here, and that is part of the point.
A lot of good product code is just careful code, not clever code.
Moderation was not optional
The moment you allow anonymous submissions, moderation stops being a nice-to-have.
Even if your product is small, public forms eventually attract junk:
- spam links
- repeated nonsense
- promotional messages
- low-effort duplicate posts
I did not want LoopSignal to become one of those "technically open, practically unusable" public boards.
So every new submission starts as pending.
That gives teams a lightweight review layer before something appears publicly. It also lets the product stay open to anonymous participation without making the board feel messy.
This ended up being one of the most important quality-of-life decisions in the whole product.
Open submission works much better when moderation is built in from day one.
Anti-spam without overengineering it
I did not want to begin with a complex third-party moderation stack.
Instead, I started with a simple scoring approach.
Things that add spam weight include:
- all-caps titles
- too many links
- repeated characters
- suspicious email patterns
- known spam phrases
That is enough to catch a surprising amount of bad input.
The goal here is not perfect classification. The goal is to block obvious junk while keeping the system fast and lightweight.
That is a pattern I keep coming back to in product work:
Start with the simplest version that meaningfully reduces pain.
If it turns out to be insufficient, then you have real usage data to justify something more advanced.
Optional email was better than required auth
This was another small decision that had a big product effect.
I still wanted users to be able to hear back when something changed. That is part of the whole "close the loop" idea behind LoopSignal.
But I did not want updates to require registration.
So the form allows an optional email field.
If a user includes it, they can get notified when the request is approved, planned, completed, or replied to. If they leave it blank, the feedback still gets submitted.
That gave me a much better balance:
- low friction for submitting
- enough contact info for follow-up when users want it
- no forced signup just to leave one idea
In practice, that feels much more aligned with how users actually behave.
One subtle thing this unlocked later
Building submission first clarified the rest of the product.
Once you have a clean, low-friction way to collect feedback, the next pieces become much more obvious:
- approved posts can appear on the public board
- users can vote on them
- teams can change statuses
- approved requests can create GitHub issues
- completed items can feed the changelog
That is why I think the submission flow was the real starting point of LoopSignal, even if it is not the flashiest feature.
It defined the shape of the whole system.
What I learned from building this piece
A few things became very clear while working on it.
First, friction matters more than formality.
A clean, lightweight submission experience will produce more useful feedback than a "proper" workflow with too many steps.
Second, anonymous does not have to mean uncontrolled.
If you combine rate limits, spam scoring, and moderation, you can keep the door open without letting the system turn into noise.
Third, optional identity is often better than mandatory identity.
Sometimes the best way to get signal is to make participation easy, then let users opt into deeper engagement.
And finally, building the smallest meaningful loop is a better starting point than trying to design the entire platform up front.
In LoopSignal's case, that loop started with one question:
How do I make it as easy as possible for someone to tell a product team what they want?
Everything else came after that.
What's next
In the next article, I'll go deeper into the public board itself: how approved feedback becomes visible, how voting works, and how I approached prioritization without turning the product into heavy roadmap software.
If you are building something similar, my biggest takeaway from this part is simple:
Do not start by designing the perfect feedback system.
Start by removing everything that makes feedback harder to send.
Top comments (0)