Build Circle

KINDAreplaces $89/mosaves $1,068/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort multi-daythe files for this build are in the project pack

CommunityDiscourse, self-hosted with its official installMembershipA small Node 22 service alongside itBillingStripe Checkout and the customer portal, driven by webhooksIdentityDiscourse as the single sign-on provider for your membership pagesEmailA transactional provider with a verified sending domain

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. 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

  2. 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

  3. 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

  4. 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

  5. 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

  6. 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.

VariableNeededExampleWhere the value comes from
DISCOURSE_URLrequiredhttps://community.example.comYour Discourse installation.
DISCOURSE_API_KEYsecretrequiredreplace-with-admin-api-keyDiscourse admin API settings. Server only, never in a browser.
DISCOURSE_API_USERNAMErequiredsystemThe account the API acts as. Use the system account rather than a person.
DISCOURSE_SSO_SECRETsecretrequiredgenerate-a-long-random-valueShared secret for the single sign-on flow. Must match the value configured in Discourse.
STRIPE_SECRET_KEYsecretrequiredsk_test_...Stripe dashboard. Stay in test mode until the restore drill has been done.
STRIPE_WEBHOOK_SECRETsecretrequiredwhsec_...From the registered endpoint, or printed by stripe listen during development.
MEMBERSHIP_DATABASE_URLsecretrequiredpostgres://user:pass@localhost:5432/membershipYour membership service's own database, separate from Discourse's.
TIER_GROUP_MAPrequiredbasic:members,pro:pro-membersWhich Stripe price maps to which Discourse group. The whole access model in one line.

The build, in order

  1. Discourse running properly

    A real community platform is up, on your domain, sending email that arrives.

    1. Use the official installation. Unsupported variations are where the long support threads come from, and you will not get help with them.

    2. Add the DNS records your provider requires and wait for verification. Nothing else in this build works until mail does.

    3. 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 passes
    watch 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.
  2. Spaces as groups and private categories

    Access is expressed once, as group membership, and every space respects it.

    1. Groups are the only access primitive you will use. Everything downstream adds and removes group membership and nothing else.

    2. Set both see and reply permissions to the groups. Visibility is the setting people forget.

    3. Log out, search for a phrase you posted in the private category, and confirm nothing is returned.

    done when · tick each as it passes
    watch 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.
  3. Subscriptions

    Someone can pay, and the webhook is the only thing that believes they did.

    1. 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.ts

    2. Signature verification with the endpoint secret, and a processed-events table so redelivery is a no-op.

      Files src/webhooks.ts

    3. Use the Stripe command line tool to trigger and then replay an event. Assert the second delivery changes nothing.

      terminal
      stripe listen --forward-to localhost:3000/webhooks/stripe
      stripe trigger customer.subscription.created
    done when · tick each as it passes
    watch 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.
  4. Access follows the subscription

    Paying grants access, lapsing removes it, and every change is recorded.

    1. 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.ts

    2. Access 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.ts

    3. The 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 passes
    watch 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.
  5. Single sign-on and self-service

    Members manage their own billing and never keep a second password.

    1. Its single sign-on flow signs members in to your pages. Your service stores an identifier, never a password.

      Files src/sso.ts

    2. Generate a portal session for the signed-in member. Card updates, tier changes and cancellation are all handled there.

      Files src/portal.ts

    3. Cancel 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 passes
    watch 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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more community.