DEV Community

우병수
우병수

Posted on • Originally published at techdigestor.com

I Used Canny, Productboard, and a Self-Hosted Alternative to Decide What to Build — Here's What Actually Worked

TL;DR: The loudest customer wins. That's the real algorithm behind most early-stage SaaS roadmaps, and nobody wants to admit it.

📖 Reading time: ~27 min

What's in this article

  1. The Real Problem: You're Guessing What to Build Next
  2. Why I Stopped Using Canny After 6 Months
  3. What Fider Actually Is (And What It's Not)
  4. Self-Hosting Fider: The Actual Setup (Not the Happy Path)
  5. Configuring Fider for a SaaS Feedback Loop
  6. Where Formbricks Fits In (When Fider Isn't Enough)
  7. Fider vs. Canny vs. Formbricks — Side by Side
  8. When to Pick What: Match the Tool to Your Situation

The Real Problem: You're Guessing What to Build Next

The loudest customer wins. That's the real algorithm behind most early-stage SaaS roadmaps, and nobody wants to admit it. You've got a backlog with 200 items — some submitted six months ago, some copy-pasted from a Slack rant at 11pm — and you're essentially doing sentiment analysis on whoever emailed you last. That's not product strategy, that's customer support with delusions of grandeur.

Here's what makes this worse: the squeaky-wheel problem isn't random noise. The customers who shout loudest are often your smallest accounts or your most demanding power users who represent nobody else. Meanwhile, your silent majority — the ones actually churning — never told you why they left because you never gave them a structured way to do it. A proper feedback system doesn't just collect requests; it reveals who cares about what, weighted by the accounts that actually matter to your revenue.

Paid tools exist for this. Productboard starts at $19/maker/month on their Essentials plan, which sounds fine until you realize "makers" means anyone touching the roadmap, and you're suddenly looking at $100+/month before you've validated anything. Canny is free up to a point, but the free tier limits you to one board and no private feedback — which is basically unusable for B2B where customers don't want their feature requests public. The moment you need SSO, custom domains, or API access, you're on a $360/year plan minimum. For a pre-Series A SaaS, that's a real conversation with your co-founder.

Open-source alternatives exist and they're genuinely good — Fider being the most production-ready of the bunch — but most tutorials hand you a Docker command and call it a day. They skip the reverse proxy config, the email delivery setup, the OAuth wiring, and the part where you realize Fider's environment variables are not well-documented in one place. This article covers the actual self-hosting setup for Fider end-to-end, then gives you an honest side-by-side against Canny and Formbricks so you can pick based on your specific situation rather than whoever wrote the most SEO-optimized landing page. For a broader look at building smarter with AI during development, the Best AI Coding Tools in 2026 (thorough Guide) is worth a read alongside this.

One thing I want to be upfront about: none of these tools — paid or open-source — fix a broken product instinct. What they do is replace your gut-and-Slack workflow with something you can actually audit. You can see that 47 users voted for feature X, 12 of them are on your Pro plan, and none of them have churned yet. That's a different conversation than "Sarah from Acme keeps asking about it."

Why I Stopped Using Canny After 6 Months

The free tier wall hit me on month two, not month six. Canny's limit of one board and 100 tracked users sounds fine until you realize "tracked users" means anyone who interacts with your widget — voters, commenters, even people who just open the thing without clicking. I burned through 100 tracked users during a single beta launch email. The moment I tried to see who voted for what so I could segment by plan type or company size, Canny told me to upgrade. That's the data you actually need to make a real prioritization decision, and it's gated behind a paid tier that starts at $99/month.

The export thing was the real breaking point. I wanted to pull raw vote data into a spreadsheet and cross-reference it against our MRR numbers from Stripe. Simple ask. Canny's response: upgrade to Growth. No CSV download, no API access on the free tier, nothing. You can see counts on the dashboard but you can't own the data. That's a fundamental problem — your users are telling you what they want and a third-party vendor is holding the ledger.

Productboard is genuinely more sophisticated, but "sophisticated" here means genuinely difficult to onboard onto. The three-layer model of Features → Insights → Objectives makes sense once you've read their documentation three times, but the friction of correctly tagging an insight, linking it to a feature, and then aligning that feature to an objective is non-trivial overhead for a team of two or three people. I spent a full afternoon importing customer interview notes and still wasn't sure if I'd set up the hierarchy correctly. When you just want to answer "what should we build next week," the abstraction gets in the way.

The migration problem with both tools is worse than it looks upfront. Canny stores your roadmap data in their format. Productboard stores it in theirs. Neither makes it easy to leave. When I tried to export from Canny, I got a limited JSON dump that didn't include vote timestamps or voter metadata. Productboard's export is similarly lossy — you get a flat CSV that strips the relationships between insights and features. So if you build six months of prioritization history in either tool, you're essentially starting over if you switch. That lock-in is the real cost, not the monthly subscription.

What Fider Actually Is (And What It's Not)

The thing that trips people up first: Fider doesn't try to be everything. It's a feature voting board. You post ideas, users vote, you update statuses, Fider sends email notifications to everyone who voted. That's the whole product. If you're expecting a Productboard replacement with roadmap timelines, RICE scoring, and Slack threads — stop here, that's not what this is.

Architecturally it's Go on the backend, React on the frontend, Postgres for storage. The GitHub repo is getfider/fider and I'd strongly recommend checking the release tags before you pull anything. The gap between what's on main and the latest stable tag can be meaningful — I got burned once pulling head and hitting a migration issue that wasn't present in v0.21.1. The Docker image tags track releases, so pin to a specific version in your compose file, not latest.

# docker-compose.yml — pin the version, don't trust :latest
services:
  fider:
    image: getfider/fider:0.21.1
    environment:
      DATABASE_URL: postgres://fider:password@db:5432/fider?sslmode=disable
      JWT_SECRET: your-random-secret-here
      EMAIL_NOREPLY: noreply@yourdomain.com
      # SMTP or Mailgun — configure one or emails just silently fail
      EMAIL_MAILGUN_API: your-mailgun-key
      EMAIL_MAILGUN_DOMAIN: mg.yourdomain.com
    ports:
      - "3000:3000"
Enter fullscreen mode Exit fullscreen mode

Here's what Fider handles genuinely well: collecting feature requests from users who wouldn't otherwise bother filing a ticket, surfacing which ideas have real demand through votes, tagging posts by category, moving posts through statuses (Open → Planned → Completed), and notifying every voter automatically when status changes. That last one is underrated. Users don't have to check back — they get a real email when you ship the thing they asked for. That closes the loop in a way most homegrown "submit feedback" forms never do.

What it explicitly doesn't have: no roadmap swimlane view, no prioritization framework, no native integration with Linear or Jira, no effort vs. impact scoring. You'll need to make the call yourself on what to build — Fider just shows you the vote counts. Some teams treat that as a limitation. I think it's honest. A number next to a feature request is one input, not a mandate.

The ideal operator here is a solo founder or a small team (think under 20 people) who already know how to run a Docker container on a VPS or managed host, want to own the data instead of paying $50–$400/month for Canny or UserVoice, and don't need the voting board to sync bidirectionally with their project management tool. If you're at a company where someone will ask "does this integrate with Salesforce," Fider will disappoint them. If you're someone who just wants to stop guessing what users actually want and can stand up a Postgres instance, it'll earn its keep fast.

Self-Hosting Fider: The Actual Setup (Not the Happy Path)

The thing that almost broke me on the first deploy wasn't Fider itself — it was a silent email failure that looked like a successful startup. The container was running, the site loaded, but every "verify your email" link went nowhere. Took me 40 minutes to figure out EMAIL_NOREPLY didn't match my verified sender domain on Resend. No error in the logs. Just… nothing. So let's skip the happy path and talk about what actually matters.

What You Need Before You Touch Docker

I spun this up on a Hetzner CX11 — €3.79/month at current pricing, 2 vCPUs, 2GB RAM, 20GB disk. Fider is not a hungry app; that box runs it fine with headroom to spare. You need Docker 24+ and Docker Compose v2 (the plugin version, not the standalone docker-compose binary — the command is docker compose with a space). You also need a Postgres instance. I used a managed one on Hetzner Database Cloud for €4/month rather than running Postgres in a second container, because I don't want to think about pg_dump crons on a feedback board. For SMTP, Resend's free tier gives you 3,000 emails/month — more than enough unless your SaaS has a very vocal user base.

Start From the Repo's docker-compose.yml, Not a Blank File

Grab the official file directly from the Fider GitHub repo. Don't transcribe it from a blog post (including this one). The env var names have changed between versions and a stale example will waste your afternoon.

curl -o docker-compose.yml \
  https://raw.githubusercontent.com/getfider/fider/main/docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

Then edit it. Here's the slice that matters, with the vars that actually trip people up annotated:

services:
  fider:
    image: getfider/fider:stable
    ports:
      - "3000:3000"
    environment:
      # Postgres connection — include sslmode=require if using managed DB
      DATABASE_URL: postgres://fider:yourpassword@db-host:5432/fider?sslmode=require

      # Must be cryptographically random, minimum 64 bytes (512 bits)
      # Generate with: openssl rand -hex 64
      JWT_SECRET: "your_64_byte_hex_string_here"

      # This MUST match the "From" domain you've verified with Resend/Postmark
      # If your Resend sender is noreply@feedback.yoursaas.com, this must match exactly
      EMAIL_NOREPLY: noreply@feedback.yoursaas.com

      EMAIL_SMTP_HOST: smtp.resend.com
      EMAIL_SMTP_PORT: 465
      EMAIL_SMTP_USERNAME: resend
      EMAIL_SMTP_PASSWORD: "re_your_api_key_here"
      EMAIL_SMTP_ENABLE_STARTTLS: "true"
    restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

The JWT_SECRET minimum is real and enforced — Fider will refuse to start and log a validation error if it's too short. Generate it with openssl rand -hex 64 right now and put it straight into your .env file. The DATABASE_URL gotcha: if you're on a managed Postgres that requires SSL (Hetzner, Supabase, Railway all do), you need ?sslmode=require appended or you'll get connection refused in the logs with zero explanation about why SSL negotiation failed.

First Boot and Migrations

On first docker compose up, Fider auto-runs its migrations. Watch the logs with docker compose logs -f fider during this — don't just start it and walk away. A successful migration sequence looks like a list of applying migration... lines ending in server started. If you see dial tcp: connection refused and nothing else, your DATABASE_URL is wrong: wrong port, wrong host, or missing SSL mode. The error message won't tell you which one. Cross-check by running psql manually from the same host first:

psql "postgres://fider:yourpassword@db-host:5432/fider?sslmode=require" -c "\l"
Enter fullscreen mode Exit fullscreen mode

If that connects, Docker will too.

Caddy Reverse Proxy: Genuinely Two Lines

I switched from Nginx to Caddy for this exact use case — automatic HTTPS with zero cert management. Install Caddy on your VPS, then your entire /etc/caddy/Caddyfile is:

feedback.yoursaas.com {
  reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode

That's it. Caddy handles Let's Encrypt cert provisioning and renewal automatically. Point your DNS A record for feedback.yoursaas.com at the VPS IP, run sudo systemctl reload caddy, and HTTPS is live within 30 seconds. The subdomain pattern I recommend is feedback.yoursaas.com over something like yoursaas.com/feedback — Fider doesn't officially support running at a path prefix, and you'll fight it if you try.

Do This Before Anything Else: Claim the Admin Account

The first person to sign up on a freshly deployed Fider instance becomes the admin. If your VPS is on a public IP (it is), do not go get coffee after deployment. Open the URL, register your account, and verify the email immediately. This isn't hypothetical paranoia — bots and scanners hit fresh IPs constantly, and while a random person becoming admin on your feedback board isn't a catastrophic security event, it's an annoying one to clean up. If you want extra protection before going public, Caddy can add HTTP basic auth in two more lines while you complete setup:

feedback.yoursaas.com {
  basicauth /  {
    # Generate hash with: caddy hash-password
    admin $2a$14$your_bcrypt_hash_here
  }
  reverse_proxy localhost:3000
}
Enter fullscreen mode Exit fullscreen mode

Remove it once you've claimed admin and you're ready to open the board to users.

Configuring Fider for a SaaS Feedback Loop

The OAuth SSO setup is the thing most people skip and then regret. If your users have to create a separate Fider account, conversion to "actually submitting feedback" drops dramatically — people don't want another login. Fider supports generic OAuth2, which means you can wire it to whatever auth provider you're already running: Auth0, Clerk, Supabase Auth, Keycloak, whatever. The generic adapter approach is genuinely useful here because you're not waiting for Fider to ship a first-party integration.

Here's how the OAuth fields map for two common providers. In Fider's admin panel under Settings → Authentication, you'll see these exact fields:

# Auth0 mapping
Client ID:        Your Auth0 application's Client ID
Client Secret:    Your Auth0 application's Client Secret
Authorize URL:    https://YOUR_TENANT.auth0.com/authorize
Token URL:        https://YOUR_TENANT.auth0.com/oauth/token
Profile API URL:  https://YOUR_TENANT.auth0.com/userinfo
# Fider expects the profile response to have "email" and "name" keys
# Auth0's /userinfo returns both by default if you request the "openid profile email" scopes

# Clerk mapping
Client ID:        From Clerk Dashboard → OAuth Applications
Client Secret:    From Clerk Dashboard → OAuth Applications
Authorize URL:    https://YOUR_FRONTEND_API.clerk.accounts.dev/oauth/authorize
Token URL:        https://YOUR_FRONTEND_API.clerk.accounts.dev/oauth/token
Profile API URL:  https://YOUR_FRONTEND_API.clerk.accounts.dev/oauth/userinfo
# Clerk also returns "email" and "name" — no custom mapping needed
# Make sure "Email address" is required in your Clerk user settings, not optional
Enter fullscreen mode Exit fullscreen mode

One gotcha I ran into with Clerk specifically: the FRONTEND_API subdomain format changed a couple of times. If you set this up and it breaks after a Clerk update, check their dashboard for the current OAuth base URL — they don't always shout about those changes in their changelog.

The tagging strategy is worth thinking about before you start collecting posts, not after. Fider's tags are just labels, but the workflow they enable matters. I use a strict set and nothing else:

  • under-review — I've read it, I'm thinking about it, but no decision yet
  • planned — it's on the roadmap, no ETA promised
  • in-progress — actively being built right now
  • shipped — done, linked to changelog entry or release note
  • wont-build — closed with an explanation comment, not just silently dismissed

The email notification behavior is the sleeper feature here. Every time you change a post's status — or add a response comment — Fider automatically emails everyone who voted on that post. No configuration required, it just works. This single mechanism is what turns Fider from a suggestion box into an actual feedback loop. Users notice when their specific request gets a "planned" tag with a comment from you. I've had users reply to those emails saying it was the first time they felt like a SaaS product was actually listening. Don't underestimate how low the bar is — most products ghost their voters entirely.

The REST API is underdocumented but functional. The most useful endpoint for internal dashboards is GET /api/v1/posts, which returns posts with vote counts, status, and tags. You need an API key from Settings → API Keys.

# Pull all posts sorted by votes, filter to 'open' status
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://feedback.yourdomain.com/api/v1/posts?limit=50&status=open"

# Response shape you care about:
# {
#   "id": 42,
#   "title": "Export to CSV",
#   "votesCount": 87,
#   "status": "open",
#   "tags": ["under-review"],
#   "createdAt": "2024-03-15T10:22:00Z"
# }

# Pipe this into your internal tooling — a simple cron job that hits this
# endpoint and writes to a Postgres table is enough to build a priority-sorted
# feature backlog that's always in sync with user demand
Enter fullscreen mode Exit fullscreen mode

The status filter accepts open, planned, started, completed, and declined — these are Fider's internal status values, which are separate from your custom tags. The practical move is to use the API status for pipeline tracking in your internal tools, and use tags for the human-readable labels your users see. Don't try to make one system do both jobs.

Where Formbricks Fits In (When Fider Isn't Enough)

Fider is great at surfacing demand signals — users upvote, comment, the popular stuff floats up. But it's fundamentally a wishlist tool. It tells you what people want to add, not why they churned last month, not what nearly killed their signup flow, not what they're silently tolerating. That gap is where Formbricks earns its place.

Formbricks lets you embed targeted micro-surveys at specific points in your app — post-signup, pre-cancellation, after a feature is first used. The question I've gotten the most signal from is something like "What almost stopped you from signing up?" — you'd be shocked how many product assumptions that single question destroys. You can self-host the whole thing via Docker, which matters if you're collecting anything sensitive or need GDPR compliance without paying for a DPA negotiation with a SaaS vendor.

The official one-liner to get it running:

curl -fsSL https://raw.githubusercontent.com/formbricks/formbricks/main/docker/docker-compose.yml | docker-compose -f - up -d
Enter fullscreen mode Exit fullscreen mode

That spins up the Next.js app, a Prisma-backed Postgres schema, and a few supporting services. Fair warning: this is not a lightweight install. Fider is a single Go binary that'll run on a $5 VPS without complaint. Formbricks needs more RAM, more config, and you'll want to budget at least a 2-core/4GB instance or things get sluggish under any real load. Also double-check that your Docker Compose version supports the syntax in that file — I hit a silent failure on an older v1 install before switching to Compose v2.

The combo I actually recommend for early-stage SaaS: Fider for the public-facing feature roadmap and ongoing voting, Formbricks for in-app moments where you need qualitative depth. They solve adjacent problems and don't overlap much. Fider answers "what should we build next based on volume of requests." Formbricks answers "what's broken in the experience right now that nobody thought to request a fix for."

One honest caveat that'll save you frustration: the self-hosted Formbricks build consistently lags behind their cloud product. Not by a little — sometimes features that are clearly in their UI screenshots don't exist yet in the self-hosted release. Before you plan a workflow around a specific survey type or integration, pull their CHANGELOG.md directly from the repo and verify the feature landed in a tagged release, not just in main. I've been burned by this once, built an onboarding flow expecting a feature from their docs that turned out to be cloud-only at the time.

Fider vs. Canny vs. Formbricks — Side by Side

The comparison that actually matters isn't features — it's what happens when you outgrow the free tier or need to move your data. That's where the differences between these tools stop being theoretical.

Dimension

Fider

Canny

Formbricks

Hosting

Self-hosted only

Cloud only

Self-hosted or cloud

Free Tier

Unlimited (you pay infra)

1 board / 100 tracked users

Cloud free tier + self-host

Data Ownership

Full — it's your DB

No raw export on free tier

Full on self-hosted

Voter Limit

Unlimited

100 tracked users on free

Not a voting board

API

REST API included

REST API (paid plans)

REST API included

SSO Support

OAuth2 built-in

Paid plans only

Paid/self-hosted configs

~Cost at 1k MAU

~$6/mo VPS (e.g. Hetzner CX11)

Check their pricing page — changes frequently

Self-host: ~$6/mo; cloud: check site

Dealbreaker

You maintain the infra

Data lock-in

Wrong tool for voting boards

Canny's 100 tracked-user cap is the thing that bites teams hardest. "Tracked users" isn't page views — it's users Canny actually identifies and stores data on. Once you integrate it with your auth system and pass user identities, you hit that ceiling faster than you'd expect on even a modest SaaS product. After that, you're looking at their paid tiers, and the jump in price is steep enough that teams frequently switch to self-hosted Fider instead of paying up.

The data lock-in issue with Canny is real and structural, not just a billing complaint. On the free tier there's no raw export of votes, voters, or post metadata. If you decide to migrate after a year of feedback collection, you're doing it manually or losing the history. With Fider, your data lives in a Postgres database you control — I can run a straight SQL dump and pipe it anywhere. That's the actual difference between open-source and SaaS feedback tooling.

Formbricks confuses people because it looks adjacent to this space but solves a different problem. It's a survey and qualitative research tool — NPS flows, onboarding questionnaires, in-app micro-surveys. You'd use it to understand why users behave a certain way, not to let users vote on what to build next. Using Formbricks as a feature voting board is like using Typeform as a project tracker — technically possible, operationally painful. If you need both qualitative insight and a voting board, run Formbricks alongside Fider, not instead of it.

One thing the table can't capture: operational maturity. Fider's Docker setup is genuinely straightforward, but you're owning the upgrade cycle, backup strategy, and any downtime. Canny handles all of that silently in the background — if that ops overhead matters to your team size, it's a legitimate reason to pay the premium, just go in with clear eyes about the data trade-off you're making.

# Fider minimal docker-compose — this is the actual starting point
version: '3'
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: fider
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: fider
    volumes:
      - pgdata:/var/lib/postgresql/data

  app:
    image: getfider/fider:stable
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://fider:changeme@db:5432/fider?sslmode=disable
      JWT_SECRET: your-random-secret-here
      EMAIL_NOREPLY: noreply@yourdomain.com
      # EMAIL_MAILGUN_* or EMAIL_SMTP_* depending on your setup
    depends_on:
      - db

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

SSO is where Fider quietly wins for B2B SaaS teams. OAuth2 support is baked in at zero extra cost — you configure it against your existing identity provider and users log into your feedback board with the same credentials they use for your app. Canny gates this behind paid plans. If you're building for enterprise buyers who care about SSO as a baseline expectation, that's not a minor footnote.

When to Pick What: Match the Tool to Your Situation

The thing that trips most founders up is over-engineering their feedback stack before they have enough users to generate meaningful signal. I've watched teams spend three weekends configuring a self-hosted feedback portal for a product with eleven active users. That's backwards. The sophistication of your tooling should lag behind your user count deliberately.

Pre-revenue, fewer than ~20 users: Close this tab. Open a Notion page, write down what people are complaining about, then call them. Seriously. No tool in this article will help you more than a 30-minute Zoom call with someone who almost churned. Voting requires a crowd — with five beta users, one loud opinion will dominate any upvote count and you'll optimize for the wrong thing.

50–500 users, want to look credible without burning cash: Self-host Fider. It runs fine on a $6/mo Fly.io machine or a small Hetzner VPS (CX11 at ~€3.79/mo works). You get a public roadmap board, email notifications, status updates, and OAuth login. The setup is maybe two hours if you've done Docker before:

# docker-compose.yml — the real one, not a dumbed-down version
services:
  fider:
    image: getfider/fider:stable
    ports:
      - "3000:3000"
    environment:
      BASE_URL: https://feedback.yourapp.com
      DATABASE_URL: postgres://fider:password@db/fider?sslmode=disable
      JWT_SECRET: some-long-random-string-here
      EMAIL_NOREPLY: noreply@yourapp.com
      EMAIL_MAILGUN_API: your-mailgun-key
      EMAIL_MAILGUN_DOMAIN: mg.yourapp.com
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: fider
      POSTGRES_PASSWORD: password
      POSTGRES_DB: fider
Enter fullscreen mode Exit fullscreen mode

You need qualitative depth, not just vote counts: Bolt Formbricks onto Fider. These two complement each other without overlapping — Fider tells you what users want ranked by demand, Formbricks tells you why they're asking, why they churned, or why they didn't activate. Run your NPS survey in Formbricks, then cross-reference low NPS users against which features they voted for in Fider. That's where you find the actual retention levers. Formbricks self-hosts cleanly and also has a free cloud tier up to 500 responses/month if you want to test it before committing to ops.

Zero ops appetite, budget available: Canny's paid tier starts at $79/mo and I'll say plainly — it's worth it if you'd rather ship features than maintain infrastructure. You get autopilot feature tagging from Intercom/Zendesk tickets, changelog publishing, and Jira/Linear sync. The ROI math is easy: if maintaining your own stack costs even one hour a week of a senior developer's time, Canny pays for itself. No shame in buying convenience when your time has higher-value uses.

Regulated industries (HIPAA, GDPR with data residency requirements): Self-hosting isn't optional here — it's the only path that actually closes your compliance checklist. Both Fider and Formbricks give you full data residency control. Run them on infrastructure inside your required region (AWS eu-west-1, Hetzner Falkenstein, whatever your DPA requires), configure your own TLS, and they don't phone home. Canny, even on enterprise plans, is still SaaS with US-based infrastructure by default — their BAA situation for HIPAA is murky enough that I wouldn't bet a compliance audit on it without your legal team signing off first.

Three Gotchas I Hit That the Docs Don't Mention

I burned a combined four hours on these three issues across two different Fider deployments. None of them are documented anywhere obvious. Putting them here so you don't repeat my mistakes.

Gotcha 1: Silent email failures with STARTTLS

If your SMTP host requires STARTTLS — SendGrid, Postmark, and most self-hosted mail servers using port 587 all do — and you leave EMAIL_SMTP_ENABLE_STARTTLS=false, Fider won't throw an error. It'll log a successful send attempt. Your users just never get the email. I spent 45 minutes checking Fider container logs, restarting the service, and re-entering SMTP credentials before I opened the mail server logs and saw connection negotiation failing silently on the SMTP side. The correct config for port 587 looks like this:

EMAIL_SMTP_HOST=smtp.yourprovider.com
EMAIL_SMTP_PORT=587
EMAIL_SMTP_USERNAME=your@email.com
EMAIL_SMTP_PASSWORD=yourpassword
EMAIL_SMTP_ENABLE_STARTTLS=true   # STARTTLS required for port 587, not optional
Enter fullscreen mode Exit fullscreen mode

Use port 465 with SSL instead and you don't need STARTTLS, but most transactional providers have deprecated 465. Check your mail server's auth logs first, always — Fider's own logs will tell you nothing useful here.

Gotcha 2: The OAuth callback URL is unforgiving about trailing slashes

This one is a classic but it hit me because Caddy's automatic HTTPS redirect behavior adds a trailing slash to the root domain in some configs. If your Caddyfile proxies feedback.yourdomain.com/ (with trailing slash) but your GitHub or Google OAuth app has the callback registered as https://feedback.yourdomain.com/oauth/callback (without), you get a redirect URI mismatch error and a vague OAuth failure page. The fix is making sure both sides are identical — character for character:

# Caddyfile — no trailing slash on the host block
feedback.yourdomain.com {
    reverse_proxy fider:3000
}

# OAuth callback URL registered in your provider's app config
https://feedback.yourdomain.com/oauth/callback
Enter fullscreen mode Exit fullscreen mode

Also double-check whether you've registered http:// vs https:// during local dev and then forgotten to update the provider settings when you went to production. It's always that.

Gotcha 3: Post list performance degrades hard past ~500 entries

Fider's admin post list doesn't paginate efficiently at scale. Past roughly 500 posts, the admin panel slows noticeably — search gets sluggish and bulk status changes start timing out. If you're migrating a multi-year Canny backlog using their CSV export or the API, don't just dump everything in at once and assume it'll be fine. I learned this by importing ~800 posts in a single batch script and watching the admin UI become nearly unusable the next morning. The smarter approach:

  1. Import in batches of 100–150 posts using Fider's REST API
  2. After each batch, run a few representative search queries in the admin UI and time them manually
  3. Add a Postgres index on the posts table's title column if you're self-hosting and control the DB — Fider doesn't create a full-text index by default

The Postgres side of this is worth inspecting directly. Connect to the DB and run EXPLAIN ANALYZE SELECT * FROM posts WHERE title ILIKE '%your search term%' — if you see a sequential scan on a large table, that's your problem. A GIN index on a tsvector column will fix it, but you're now maintaining a schema change outside Fider's migration path, so document that carefully if you ever plan to upgrade Fider versions.

My Current Setup After a Year of Iteration

The thing that surprised me most after a full year running this setup: a 2 vCPU, 4GB RAM Hetzner CX21 at €4.51/month has never once been the bottleneck. Fider is genuinely lightweight. I threw thousands of posts, votes, and comment threads at it and the p95 response time barely moved. If you're self-hosting and agonizing over instance size, start here and only upsize when your metrics tell you to.

Everything runs on that single machine via Docker Compose — Fider, Postgres, and a small cron container. The Postgres data lives on a Docker volume, which is fine at this scale. I'm not losing sleep over it because I have a daily pg_dump piped to Hetzner's object storage. That said, if you're doing this for a team of 50+ with hundreds of active voters, skip the self-managed Postgres entirely and pay for a managed instance. The operational headache of a corrupted volume is not worth the $15/month you're saving.

# docker-compose.yml (trimmed to the relevant bits)
services:
  fider:
    image: getfider/fider:stable
    environment:
      DATABASE_URL: postgres://fider:password@db:5432/fider?sslmode=disable
      EMAIL_MAILGUN_API: ""
      EMAIL_SMTP_HOST: "smtp.resend.com"
      EMAIL_SMTP_PORT: "465"
      EMAIL_SMTP_USERNAME: "resend"
      EMAIL_SMTP_PASSWORD: "${RESEND_API_KEY}"
      EMAIL_SMTP_ENABLE_STARTTLS: "false"
    depends_on:
      - db

  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

I switched to Resend for transactional email after getting burned by Mailgun's new regional endpoint requirements. Resend's free tier gives you 3,000 emails/month and 100/day — vote notifications for a typical SaaS feedback board are nowhere near that ceiling unless you have a very engaged audience. The SMTP bridge config above just works with Fider's built-in mailer, no custom code required. The one gotcha: make sure EMAIL_SMTP_ENABLE_STARTTLS is false on port 465 — I spent an embarrassing amount of time debugging that.

The actual ritual that makes this worth running: every Monday morning I hit the API and pull the top posts before the team standup.

# Pull top 10 voted posts — pipe through jq for readable output
curl -s "https://feedback.yourdomain.com/api/v1/posts?limit=50&sort=votes" \
  -H "Authorization: Bearer ${FIDER_API_KEY}" \
  | jq '[.[] | {title: ".title, votes: .votesCount, status: .status}] | .[0:10]'"
Enter fullscreen mode Exit fullscreen mode

We review these 10 as a team, map them against what's already in the sprint, and explicitly decide what to act on versus what to defer. The whole thing takes 15 minutes and has replaced a lot of the "but users keep asking for X" arguments in planning — because now you can just point at the number and the status field.

The one thing I'd genuinely change about Fider: there's no native Slack webhook for new post notifications. I want to know the moment someone submits something, not wait for Monday. So I wrote a small polling script that runs every 10 minutes via a cron container, checks for posts newer than the last run, and fires a Slack webhook. It's maybe 40 lines of Python and it works, but it's the kind of thing that should be a two-field config option in the dashboard. If you want the script, the core logic is just comparing post.createdAt against a timestamp you persist in a local file or a single-row Postgres table — nothing clever required.


Disclaimer: This article is for informational purposes only. The views and opinions expressed are those of the author(s) and do not necessarily reflect the official policy or position of Sonic Rocket or its affiliates. Always consult with a certified professional before making any financial or technical decisions based on this content.


Originally published on techdigestor.com. Follow for more developer-focused tooling reviews and productivity guides.

Top comments (0)