Build Circle
KINDAreplaces $89/mosaves $1,068/yrback to the verdict
Discourse self-hosted as the community itself, with private categories as spaces, a small membership service that takes Stripe subscriptions and grants or revokes group access as they start and lapse, single sign-on so members never manage a second password, and email notifications that actually arrive.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- have readytypically 10 to 30 USD a month for a small community
Why Discourse is not lightweight, and an undersized server produces a community that is slow in a way members notice immediately.
Get it Check the current official requirements before choosing, then provision accordingly. Its own installation guide is the authority on the current figure. open ↗
Verify
The official installer completes and the site loads - have readyroughly 10 to 15 USD a year
Why Needed for the site, the certificate and the email sending domain records.
Get it Any registrar. You will add A records and several email verification records.
Verify
The hostname resolves and the email records validate at your provider - API keyfree tiers typically cover a few thousand messages a month; a busy community will exceed that
Why Discourse is unusable without it. Signup, invitations, password reset and every notification depend on outbound mail arriving.
Get it Create an account, add the DNS records they specify for domain verification, and take the SMTP credentials. Verification propagation is the slow step, so start it first.
Verify
A test message from the server reaches an inbox and is not marked spam - API keyfree in test mode; live payments cost per transaction plus a further fee on subscriptions in most regions
Why Phase 3 needs the secret key and a webhook signing secret. Test mode does everything except move money.
Get it Sign up at stripe.com, take the keys from Developers then API keys, and get the webhook secret from the endpoint you register or from the listen command. open ↗
Verify
stripe listen prints a signing secret and a triggered event reaches your endpoint - API keyfree
Why The membership service adds and removes group members through it. It is a powerful credential and belongs only on the server.
Get it Create it in the Discourse admin API settings, scoped as narrowly as the platform allows.
Verify
A group membership change through the API succeeds - decidefree, except for the time it will take you every week afterwards
Why A paid community with no policy makes one up in its first crisis, which is the worst time to write it.
Get it Write what is not allowed, who decides and how someone appeals, before you invite anyone. One page is enough.
Verify
The policy is written and the platform settings reflect it
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- Discourse owns users, posts, categories and groups. Your service owns only the commercial relationship. - members(id, discourse_user_id, email, stripe_customer_id, created_at) - subscriptions(id, member_id, stripe_subscription_id, plan, state, current_period_end, cancel_at_period_end) - grants(id, member_id, group_name, granted_at, revoked_at, reason) · an audit trail of every access change - webhook_events(id, stripe_event_id, type, processed_at) · the idempotency record, because Stripe redelivers - The rule that keeps this small: a subscription state change is the only thing that ever adds or removes someone from a group.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DISCOURSE_URL | required | https://community.example.com | Your Discourse installation. |
DISCOURSE_API_KEYsecret | required | replace-with-admin-api-key | Discourse admin API settings. Server only, never in a browser. |
DISCOURSE_API_USERNAME | required | system | The account the API acts as. Use the system account rather than a person. |
DISCOURSE_SSO_SECRETsecret | required | generate-a-long-random-value | Shared secret for the single sign-on flow. Must match the value configured in Discourse. |
STRIPE_SECRET_KEYsecret | required | sk_test_... | Stripe dashboard. Stay in test mode until the restore drill has been done. |
STRIPE_WEBHOOK_SECRETsecret | required | whsec_... | From the registered endpoint, or printed by stripe listen during development. |
MEMBERSHIP_DATABASE_URLsecret | required | postgres://user:pass@localhost:5432/membership | Your membership service's own database, separate from Discourse's. |
TIER_GROUP_MAP | required | basic:members,pro:pro-members | Which Stripe price maps to which Discourse group. The whole access model in one line. |
The build, in order
Discourse running properly
A real community platform is up, on your domain, sending email that arrives.
Use the official installation. Unsupported variations are where the long support threads come from, and you will not get help with them.
Add the DNS records your provider requires and wait for verification. Nothing else in this build works until mail does.
Invite a real address, sign up, post, and receive the notification. Each step failing has a different cause, so test them separately.
done when · tick each as it passeswatch out- Discourse is effectively unusable without working outbound email, because signup, invitations, password reset and notifications all depend on it. Verify the sending domain with the DNS records your provider specifies before you configure anything else, because propagation takes time and everything else waits on it.
Spaces as groups and private categories
Access is expressed once, as group membership, and every space respects it.
Groups are the only access primitive you will use. Everything downstream adds and removes group membership and nothing else.
Set both see and reply permissions to the groups. Visibility is the setting people forget.
Log out, search for a phrase you posted in the private category, and confirm nothing is returned.
done when · tick each as it passeswatch out- A category that is visible but not readable tells non-members exactly what they are missing and leaks topic titles in search and in digest emails. Set visibility as well as read permission, and check what a logged-out visitor sees rather than assuming.
Subscriptions
Someone can pay, and the webhook is the only thing that believes they did.
Prices defined in Stripe, never passed from the client. Attach your own member identifier as metadata so the webhook can find them.
Files
src/checkout.tsSignature verification with the endpoint secret, and a processed-events table so redelivery is a no-op.
Files
src/webhooks.tsUse the Stripe command line tool to trigger and then replay an event. Assert the second delivery changes nothing.
terminalstripe listen --forward-to localhost:3000/webhooks/stripe stripe trigger customer.subscription.created
done when · tick each as it passeswatch out- Granting access on the success redirect is the classic mistake and it is worse here than usual, because the redirect can be replayed and it hands out community access rather than a download. The webhook is the truth; the redirect is a thank-you page.
Access follows the subscription
Paying grants access, lapsing removes it, and every change is recorded.
Add and remove group members with an API key scoped as narrowly as the platform allows. This key is powerful, so treat it accordingly.
Files
src/discourse.tsAccess ends at the period end for a cancellation, and only at a terminal subscription state for payment failure. A scheduled job handles period ends.
Files
src/access.tsThe audit table is what lets you answer why someone lost access, which will be asked.
Files
src/audit.ts
done when · tick each as it passeswatch out- Removing access the moment a card fails punishes people for an expired card rather than for leaving, and Stripe will retry that payment for days. Act on the subscription reaching a terminal state or the period ending, not on the first failed invoice.
Single sign-on and self-service
Members manage their own billing and never keep a second password.
Its single sign-on flow signs members in to your pages. Your service stores an identifier, never a password.
Files
src/sso.tsGenerate a portal session for the signed-in member. Card updates, tier changes and cancellation are all handled there.
Files
src/portal.tsCancel through the portal and watch the webhook schedule the removal. If this needs a human, it is not finished.
done when · tick each as it passeswatch out- Building your own account and password system beside Discourse gives every member two identities that drift apart, and gives you a password database you did not need. Let the forum be the identity provider and hold no credentials at all.
Backups, moderation and the restore drillproduct builder
The community survives a server loss, and someone is responsible for what is posted.
Discourse's own backup plus your membership database, dated, several generations, off the server.
Files
scripts/backup.shOn a clean server, restore both, then run a reconciliation that compares group membership against active subscriptions and reports differences.
Files
scripts/reconcile.tsREADME.mdWhat is not allowed, who decides, and how someone appeals. Then set the platform's trust levels and flags to match. A community without this has one anyway, made up under pressure.
Files
docs/moderation.md
done when · tick each as it passeswatch out- Restoring the forum without the membership database leaves groups populated from a backup that no longer matches who is paying, so lapsed members keep access and new ones have none. Restore both together and reconcile group membership against subscriptions afterwards.
That is the whole plan for Circle. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The polished member experience, which is a large part of what the subscription buys.
- Native mobile applications with push notifications.
- Live events, courses and the wider bundled feature set.
- Anyone else carrying uptime, spam and abuse.
- polished member UX
- mobile apps
- payments
- course/events integration
- automations
- migrations
- community admin
- An annual plan, which is a second Stripe price and a mapping entry
- A trial period, handled entirely by Stripe rather than by your own logic
- A monthly reconciliation report emailed to you, so drift is noticed without being looked for
Need the files? The project pack on the verdict page hands your agent the whole brief · more community.