A multi-tenant web platform that helps communities launch and sustain cooperative gathering spaces and member-run organizations. MaybeOS unifies org setup, membership & dues, events, space booking, community messaging & governance, and impact measurement into one integrated suite.
Modular monolith with clear module boundaries, designed to evolve into services.
maybeos-suite/
├── apps/
│ ├── api/ # NestJS backend (TypeScript)
│ │ ├── src/
│ │ │ ├── modules/
│ │ │ │ ├── auth/ # JWT + magic links, RBAC
│ │ │ │ ├── org/ # OrgOS - tenant management
│ │ │ │ ├── member/ # MemberOS - memberships & tiers
│ │ │ │ ├── events/ # EventsOS - events, RSVP, feeds
│ │ │ │ ├── space/ # SpaceOS - rooms & bookings
│ │ │ │ ├── commons/ # CommonsOS - posts, governance
│ │ │ │ ├── impact/ # ImpactOS - surveys & metrics
│ │ │ │ ├── stripe/ # Stripe integration
│ │ │ │ ├── email/ # Email automation (Postmark)
│ │ │ │ └── calendar/ # Google Calendar sync
│ │ │ ├── common/ # Guards, decorators, filters
│ │ │ └── config/ # Prisma, app config
│ │ └── prisma/
│ │ ├── schema.prisma # Full database schema
│ │ └── seeds/ # Demo seed data
│ └── web/ # Next.js frontend (App Router, TypeScript)
│ ├── app/
│ │ ├── (auth)/ # Login, register, magic link
│ │ ├── (dashboard)/ # Admin & member portal
│ │ └── (public)/ # Public events, org pages
│ ├── components/ # UI components
│ └── lib/ # API client, auth store
├── packages/
│ ├── shared/ # Shared types, constants, domain events
│ └── embed/ # Embeddable calendar widget
├── docker-compose.yml
└── package.json # Workspace root
| Module | Purpose | Key Features |
|---|---|---|
| OrgOS | Organization setup | Tenant creation, locations, branding, RBAC, settings |
| MemberOS | Membership & dues | Tiers, Stripe subscriptions, directory, CSV import |
| EventsOS | Events & gatherings | CRUD, RSVP + waitlist, public pages, JSON/ICS feeds |
| SpaceOS | Room booking | Availability rules, conflict detection, Google Calendar sync |
| CommonsOS | Social & governance | Channels, posts, proposals, voting, moderation |
| ImpactOS | Impact measurement | Surveys, belonging/loneliness metrics, dashboards, export |
- Frontend: Next.js 15 (App Router), TypeScript, Tailwind CSS, Zustand, Lucide icons
- Backend: NestJS 10, TypeScript, Prisma ORM, Passport JWT
- Database: PostgreSQL 16 with row-level tenancy (
orgIdon all tables) - Scheduled work: a Netlify Scheduled Function every 15 minutes (D-022). BullMQ and Redis were removed in D-007 — a serverless deployment had no worker to drain the queue.
- Integrations: Stripe Billing, Google Calendar API, Postmark email
- Auth: Email/password + magic links, JWT tokens, RBAC (Admin/Staff/Member/Guest)
- Node.js >= 20
- PostgreSQL 15+ — Docker Compose provides one, or use a hosted database
- Stripe account (test mode) for payment features
- Google Cloud project (optional, for calendar sync)
- Postmark account (optional, for emails)
git clone <repo-url> maybeos-suite
cd maybeos-suite
npm installdocker compose up -dThis starts PostgreSQL on port 5432. Alternatively, point DATABASE_URL at a
hosted database — the deployed environments use Supabase.
There is no Redis. The queue it backed was removed (D-007) because a serverless deployment has no worker to drain it; email sends directly.
cp .env.example .envEdit .env with your credentials:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/maybeos_dev"
JWT_SECRET="generate-a-random-64-char-string"
STRIPE_SECRET_KEY="sk_test_..." # from Stripe dashboard
STRIPE_WEBHOOK_SECRET="whsec_..." # from stripe listen
GOOGLE_CLIENT_ID="" # optional
POSTMARK_API_TOKEN="" # optional (logs emails in dev)cd apps/api
npx prisma generate
npx prisma migrate deploy # applies prisma/migrations
npm run db:seeddb:seed refuses to run against a non-local database without an explicit
override, so it cannot be pointed at production by accident.
Schema changes go through prisma migrate, never prisma db push — see
apps/api/prisma/migrations/README.md
for why, and for the drift check to run before a release.
From the repo root:
npm run devThis starts:
- API at
http://localhost:3001(Swagger docs at/api/docs) - Web at
http://localhost:3000
All passwords: password123
| Role | Org Role | |
|---|---|---|
admin@maybeos.app |
Platform Admin | Org Admin |
maya@sunrise.coop |
User | Org Admin (Steward tier) |
jordan@sunrise.coop |
User | Staff (Supporter tier) |
alex@example.com |
User | Member (Supporter tier) |
sam@example.com |
User | Member (Community tier) |
Demo organization: Sunrise Community Space (slug: sunrise)
Swagger UI is available at http://localhost:3001/api/docs when the API is running.
POST /api/auth/register # Create account
POST /api/auth/login # Email + password login
POST /api/auth/magic-link # Request magic link
GET /api/auth/magic-link/verify # Verify magic link token
GET /api/auth/profile # Get current user profile
POST /api/orgs # Create organization
GET /api/orgs/:orgId # Get org details
GET /api/orgs/by-slug/:slug # Public org lookup
PATCH /api/orgs/:orgId # Update org (Admin)
POST /api/orgs/:orgId/locations # Add location (Admin)
GET /api/orgs/:orgId/members # List members
GET /api/orgs/:orgId/tiers # List membership tiers (public)
POST /api/orgs/:orgId/tiers # Create tier (Admin)
POST /api/orgs/:orgId/members/import # CSV import (Admin)
POST /api/orgs/:orgId/events # Create event
GET /api/orgs/:orgId/events/public # Public events (no auth)
GET /api/orgs/:orgId/events/feed.json # JSON feed (cacheable)
GET /api/orgs/:orgId/events/feed.ics # ICS calendar feed
POST /api/orgs/:orgId/events/:id/rsvp # RSVP
POST /api/orgs/:orgId/events/:id/check-in/:userId # Check in
POST /api/orgs/:orgId/rooms # Create room (Admin)
GET /api/orgs/:orgId/rooms # List rooms
POST /api/orgs/:orgId/rooms/:id/bookings # Create booking
POST /api/orgs/:orgId/bookings/:id/approve # Approve (Admin/Staff)
GET /api/orgs/:orgId/my-bookings # User's bookings
POST /api/orgs/:orgId/channels # Create channel
POST /api/orgs/:orgId/channels/:id/posts # Create post
POST /api/orgs/:orgId/channels/:id/proposals # Create proposal
POST /api/orgs/:orgId/proposals/:id/vote # Cast vote
GET /api/orgs/:orgId/proposals/:id # Get proposal + tallies
POST /api/orgs/:orgId/surveys # Create survey
POST /api/orgs/:orgId/surveys/:id/respond # Submit response
GET /api/orgs/:orgId/impact/dashboard # Impact dashboard
GET /api/orgs/:orgId/surveys/:id/export # Export CSV
POST /api/orgs/:orgId/checkout # Create Stripe Checkout session
POST /api/orgs/:orgId/billing-portal # Create Billing Portal session
POST /api/stripe/webhooks # Stripe webhook handler
- Create a Stripe account and get test API keys
- Add keys to
.env - For local webhook testing:
stripe listen --forward-to localhost:3001/api/stripe/webhooks
- Copy the webhook signing secret to
STRIPE_WEBHOOK_SECRET
- Join + Pay: Member selects tier -> Stripe Checkout -> Webhook confirms subscription -> Member activated
- Manage Billing: Member clicks "Manage Billing" -> Stripe Billing Portal -> Update payment method, cancel, etc.
- Dunning:
invoice.payment_failedwebhook -> Status set to PAST_DUE -> Dunning email queued
- Create a Google Cloud project, enable Calendar API
- Create OAuth 2.0 credentials (Web application)
- Set redirect URI to
http://localhost:3001/api/calendar/oauth/callback - Add credentials to
.env
- Admin connects room to Google Calendar via OAuth
- When a booking is approved, event is created on the shared calendar
- Free/busy checks query Google Calendar for conflicts
- Updates and cancellations are synced bidirectionally
Embed the events calendar on any website:
<div id="maybeos-calendar"></div>
<script
src="https://your-app.com/embed/calendar.js"
data-org="sunrise"
data-theme="light"
data-api="https://api.your-app.com"
></script>import { MaybeOSCalendarWidget } from '@maybeos/embed';
// Initialize in useEffect
const widget = new MaybeOSCalendarWidget({
orgSlug: 'sunrise',
apiUrl: 'https://api.your-app.com',
});
widget.init();The platform emits domain events for cross-module communication:
org.created, org.updated
member.joined, member.left, member.role_changed
subscription.created, subscription.canceled, subscription.past_due
event.published, event.canceled, rsvp.created
booking.created, booking.approved, booking.rejected
post.created, proposal.opened, proposal.closed, vote.cast
survey.published, survey.submitted
- Tenant isolation: All queries scoped by
orgId; row-level tenancy enforced - Input validation: class-validator on all DTOs, global ValidationPipe
- Auth: JWT with configurable expiry, bcrypt password hashing, magic link tokens with 15-min expiry
- RBAC: Role-based guards (ADMIN, STAFF, MEMBER, GUEST) per organization
- Rate limiting: Helmet security headers enabled
- Stripe webhooks: Signature verification with idempotency checks
- Encrypted tokens: Google Calendar OAuth tokens stored encrypted
- Audit logs: All admin actions logged with actor, action, entity, timestamp
# Run both API + Web
npm run dev
# Database commands
npm run db:migrate # Run migrations
npm run db:seed # Seed demo data
npm run db:studio # Open Prisma Studio
# Individual apps
npm run dev:api # API only
npm run dev:web # Web only
# Build
npm run build # Build allSee CONTRIBUTING.md for the setup, the conventions that exist for a reason, and what a good pull request looks like. Security and privacy reports go to SECURITY.md rather than a public issue.
Apache License 2.0 — see LICENSE.
Self-hosting MaybeOS is free, and always will be (D-013). If you run it and want to help sustain the project, MaybeOS accepts gifts. MaybeItsFate LCA is a for-profit company, so those gifts are not tax deductible and must never be described as such.
The hosted service is priced separately — see D-013 in project-os/DECISIONS.md.