Ship Your SaaS in Weeks, Not Months
ASP.NET Core 10 + React 19 with auth, billing, multi-tenancy, and 40+ components. Skip the boilerplate work and focus on what makes your SaaS unique.
Everything You Need to Launch Your SaaS
A robust foundation with essential features already built and tested, ready for your custom requirements. Focus on what makes your product unique—we've built the rest.
Complete Authentication System
Email/password with email verification, OAuth (Google, GitHub), magic link authentication, TOTP-based two-factor authentication, session management with device tracking, account lockout protection, and password history validation.
Dual-Mode Stripe Billing
B2C mode (per-user subscriptions) or B2B mode (per-workspace subscriptions). Seat-based pricing with automatic adjustments, subscription lifecycle management, checkout with retention offers, customer portal integration, and complete webhook handling.
Workspace Multi-Tenancy
Workspace-based data isolation with 4-tier role system (Guest, Member, Admin, Owner). Hierarchical group management, email-based invitations, personal and team workspaces, security enforced at repository layer.
40+ Premium React Components
Two Cents Software UI included ($49 value). Professional React component library with TypeScript, automatic dark mode (OKLCH color space), accessibility-first design, Tailwind CSS v4, and full source code for customization.
Multi-Provider Email
Resend integration, SendGrid support, SMTP configuration, and console logging for development. Pre-built email templates for verification, password reset, invoices, and invitations included.
Flexible Storage System
S3-compatible (AWS, Cloudflare R2, DigitalOcean), Azure Blob Storage support, and local filesystem for development. Quota enforcement per workspace, automatic file cleanup, used for avatars, logos, and uploads.
Background Job Processing
Hangfire for reliable task execution. Recurring jobs (token cleanup, CRM sync), fire-and-forget tasks, job monitoring dashboard. Used for webhooks, emails, and automation.
HubSpot CRM Integration
Automatic contact synchronization, company sync from workspaces, deal sync from subscriptions, lifecycle stage tracking, background job processing, and extensible provider pattern.
Modern Database Architecture
PostgreSQL with Entity Framework Core 10. Feature-based schema organization, automated migrations, seed data for development, and multi-tenancy at database level.
Production-Ready Infrastructure
ASP.NET Core 10 backend (.NET 10, C# 14), React 19 frontend, TypeScript throughout, Vite for blazing-fast development, Docker configuration included, and deployment guides for major platforms.
Built on Modern, Scalable Architecture
Production-ready architecture designed for speed and scalability. Feature-based modular design keeps code organized, clean architecture patterns ensure maintainability, and modern tech stack provides the best developer experience.
Backend Architecture
The backend uses a feature-based modular architecture where each feature is self-contained with its own controllers, services, repositories, entities, DTOs, and configuration. This pattern keeps related code together, makes feature boundaries clear, and enables teams to work on different modules without conflicts.
Directory Structure
Each feature module is completely self-contained with all its code in one folder, from API endpoints to database models.
Key Architectural Patterns
Industry-standard patterns ensure code quality, maintainability, and scalability.
- Clean Architecture Pattern - Controllers → Services → Repositories → Database
- Dependency Injection Throughout - All services properly registered
- OpenAPI/Swagger Documentation - Auto-generated TypeScript client
- Feature Flags System - Enable/disable modules via configuration
- Security Audit Logging - Track sensitive operations for compliance
- Middleware Pipeline - Exception handling, security headers, workspace context
Code Example
Simple, declarative service registration keeps startup code clean and organized.
1builder.Services.AddAuthServices(builder.Configuration);2builder.Services.AddMultiTenancyServices(builder.Configuration);3builder.Services.AddBillingServices(builder.Configuration);4builder.Services.AddStorageServices(builder.Configuration);5builder.Services.AddCRMServices(builder.Configuration);
Frontend Architecture
The frontend follows a feature-based organization pattern matching the backend structure. React 19 with TypeScript provides full type safety from API to UI, while Zustand and React Query handle state management and data fetching efficiently. The component library is distributed as source code, giving you complete control over styling and behavior.
Directory Structure
Feature-based organization mirrors the backend structure. Components are separated into Two Cents Software UI (custom design system and UI library) and shadcn/ui (base primitives).
Key Technologies
Modern React ecosystem with best-in-class tools for development speed and type safety.
- React 19 + Vite - Blazing-fast development with instant HMR
- TypeScript Throughout - Full type safety with auto-generated API client
- React Router v7 - Modern file-based routing with type-safe navigation
- Zustand + React Query - Efficient state management and data fetching
- Tailwind CSS v4 - OKLCH color space with automatic dark mode
- Component Library Included - 40+ production-ready components
Code Example
Type-safe API calls with auto-generated client and React Query integration.
1// api.ts - Pure API function2export async function getWorkspaces() {3 const { data: result } = await PrivateApiClient.get<ApiResult<WorkspaceResponse[]>>("/workspaces");4 return result.data;5}67// hooks.ts - React Query wrapper8export function useWorkspaces() {9 return useSuspenseQuery<WorkspaceResponse[], ApiError>({10 queryKey: workspaceQueryKeys.list,11 queryFn: getWorkspaces,12 });13}1415// Component usage - no loading state needed16const { data: workspaces } = useWorkspaces();
Database Design
PostgreSQL with Entity Framework Core 10 provides a robust, scalable database foundation. Multi-tenancy is enforced at the repository layer through automatic workspace filtering, ensuring data isolation without complex schema-per-tenant approaches.
Key Tables
Core database entities organized by feature, with workspace-based isolation enforcing multi-tenancy.
- Users - User accounts with authentication data
- Workspaces - Tenant isolation entities
- WorkspaceMembers - User-to-workspace mappings with roles
- Groups - Hierarchical team organization
- Subscriptions - Stripe subscription tracking
- AuditLogs - Security event tracking
- RefreshTokens - Session management
- StorageItems - File metadata
Code Example
Workspace context is automatically applied to all queries, preventing cross-tenant data access.
1public async Task<List<TaskItem>> GetTasks()2{3 var workspaceId = await _currentUserService.GetWorkspaceId();45 return await _dbContext.TaskItems6 .Where(t => t.WorkspaceId == workspaceId)7 .Include(t => t.Group)8 .ToListAsync();9}
Features That Save You Months of Development
Seven major features, each built in-house to production standards. No Auth0 subscription required: our authentication system is custom-built and lives in your codebase. No hidden costs or usage limits from third-party services. Complete configuration, documentation, and code examples. Every feature includes error handling, validation, security controls, and runs entirely on your infrastructure.
Authentication & Security
Multi-method authentication out of the box with email/password, OAuth (Google, GitHub), magic links, and TOTP-based two-factor authentication. Includes session management with device tracking, account lockout protection, password history, and grace period account deletion. Everything is configurable through feature flags and JSON settings.
Why Build Auth In-House?
No monthly subscription fees per user. No usage limits that scale with your growth. No vendor lock-in or surprise price increases. This authentication system lives in your codebase, runs on your infrastructure, and scales with your business. You control the data, the features, and the costs.
- Multiple Auth Methods - Email/password, OAuth (Google, GitHub), magic links, TOTP 2FA
- Session Management with Device Tracking - Track all active sessions; revoke any session
- Account Lockout Protection - Configurable failed login attempts and lockout duration
- Password History Enforcement - Prevent reuse of last N passwords
- Grace Period Account Deletion - Soft delete with configurable recovery window
- TOTP Two-Factor Authentication - Time-based one-time passwords with backup codes
- JWT Token Security - Short-lived access tokens (15min) with refresh token rotation
- Email Verification - Optional required verification before account activation
Auth Configuration
Authentication is fully configurable through feature flags and JSON settings. Enable or disable authentication methods, configure security policies, and customize behavior without code changes.
Feature Flags control which authentication methods are available:
- Email/password authentication
- Magic link passwordless login
- OAuth providers (Google, GitHub)
- Two-factor authentication
- Email verification
- Account lockout protection
- Password reset functionality
- Account deletion with grace period
Security Settings are adjustable per your requirements:
- JWT token expiry (access tokens, refresh tokens, sessions)
- Account lockout thresholds (failed attempts, lockout duration)
- Password policies (history count to prevent reuse)
- Two-factor settings (required vs optional, backup codes)
- Token expiry times (magic links, password reset, email verification)
- Account deletion policies (grace periods, recovery windows, email notifications)
- Rate limiting (requests per time window)
- Audit logging (geolocation tracking, retention limits)
OAuth Integration for Google and GitHub is configured with client credentials and redirect URIs.
For complete setup instructions and advanced configuration options, check the authentication documentation.
Billing & Subscriptions
Choose between B2C (per-user subscriptions) or B2B (per-workspace subscriptions) business models with a single configuration change. Manage all billing operations inside your application or redirect users to Stripe's hosted Customer Portal. Includes seat-based pricing with automatic seat adjustments, subscription lifecycle management, retention offers during cancellation, complete webhook handling, and comprehensive billing management.
Dual Business Model Support
The billing system supports both B2C (per-user) and B2B (workspace-based) subscription models. You configure your business model once during initial setup. The system includes all the logic needed for either model - subscription handling, Stripe customer creation, seat management, and pricing all work differently based on your configuration.
B2C Mode: Each user has their own Stripe customer and subscription. Simpler pricing, individual billing.
B2B Mode: Each workspace has one Stripe customer and subscription. Workspace owner manages billing. Supports seat-based pricing where costs scale with team size.
In-App Billing Management vs Stripe Customer Portal
Choose your billing management approach:
In-App Management: Handle everything inside your application. Users upgrade plans, downgrade, add seats, cancel subscriptions, update billing addresses, add tax IDs (VAT, GST, EIN), and manage payment methods without leaving your app. You control the UX, branding, and entire flow. Enable the Billing__InAppManagement feature flag to activate billing details and tax ID endpoints.
Stripe Customer Portal: Redirect users to Stripe's hosted portal where they manage subscriptions, payment methods, billing details, and view invoice history. Less code to maintain, battle-tested UI, but less control over the experience. Enable with EnableBillingPortal configuration.
You pick one approach based on how much control you want over the billing experience.
Complete Feature Set
The billing system includes everything you need for production-ready subscription management:
- B2C and B2B Support - Configure once for either user-owned or workspace-owned subscriptions
- Flexible Stripe Customer Strategy - Map Stripe customers per-user or per-workspace independently
- In-App Subscription Management - Upgrade, downgrade, cancel, resume subscriptions in-app
- In-App Billing Details - Update addresses, add tax IDs (VAT, GST, EIN, ABN, 30+ types)
- In-App Payment Methods - Manage payment methods and view saved cards
- Stripe Customer Portal Option - Redirect to Stripe's hosted portal for self-service
- Automatic Seat Management - Seats adjust automatically as team members join/leave (B2B)
- Retention Offers on Cancellation - Show discount offers during cancellation to reduce churn
- Payment Grace Period - Configurable grace period for failed payments before suspension
- Complete Stripe Webhook Handling - All Stripe events handled automatically with idempotency
- Plan Upgrade/Downgrade - Control upgrades/downgrades with proration preview
- Abandoned Checkout Recovery - Resume incomplete purchases within 24 hours
- Subscription Usage Tracking - Monitor seat usage and feature limits
Configuration
The billing system is fully configurable to match your business model and customer experience preferences.
Feature Flags control billing functionality:
- Enable/disable billing system entirely
- Choose between B2B workspace subscriptions or B2C user subscriptions
- Enable seat-based pricing for team plans
- Toggle in-app billing management UI
- Control subscription lifecycle options (upgrades, downgrades, cancellations)
- Show/hide Stripe Customer Portal access
- Enable retention offers during cancellation
Business Model Settings define your subscription approach:
- Billing model (Workspace or User)
- Customer strategy (per-user or per-workspace Stripe customers)
- Seat-based pricing with automatic adjustments
- Checkout flow preferences (interval changes, billing address collection)
- Plan change policies (upgrades, downgrades, proration display)
- Cancellation behavior (immediate or at period end, retention offers)
Plan Configuration defines your pricing tiers:
- Multiple plans with monthly/yearly pricing
- Included seats and per-seat pricing for team plans
- Trial periods
- Plan visibility (public or hidden for special offers)
- Feature limits per plan (storage, API calls, members, etc.)
- Target audience (workspace, user, or both)
Stripe Integration connects to your Stripe account with webhook handling for all subscription events.
How Billing Works
Subscription Creation: Users select a plan and are redirected to Stripe's hosted checkout. After payment, Stripe sends a webhook to create the subscription record. A background job syncs the customer to HubSpot CRM. Users immediately gain access to their plan features.
Ongoing Management: Users upgrade or downgrade plans with instant proration preview. Seat adjustments happen automatically as team members join or leave (B2B mode). Failed payments trigger a configurable grace period before suspension. Cancellations can show retention offers with discounts.
Billing Details: Depending on your configuration, users either manage everything in-app (addresses, tax IDs, payment methods) or get redirected to Stripe's Customer Portal for self-service management.
For webhook configuration, testing in Stripe CLI, and handling edge cases, see the billing documentation.
Multi-Tenancy
Workspace-based data isolation with automatic filtering at the repository layer ensures users can only access data from workspaces they belong to. Dual-level permission system with workspace roles and group roles provides granular access control. Hierarchical groups enable department/team organization within workspaces. Middleware-based context injection ensures workspace isolation at every request.
Core Capabilities
The multi-tenancy system provides complete workspace isolation with automatic data scoping, dual-level permission controls, and hierarchical organization. Every query is automatically filtered by workspace context, ensuring users can only access data from workspaces they belong to.
- Workspace-Based Data Isolation - Automatic repository filtering by workspace context
- Dual-Level Permission System - Workspace roles and group roles work together
- Four-Tier Workspace Roles - Guest, Member, Admin, Owner with hierarchical permissions
- Group-Level Permissions - Viewer, Member, Lead roles within groups
- Hierarchical Group Organization - Department/team structure with nested groups
- Email-Based Invitations - Invite members with pre-assigned roles
- Domain Verification - Automatic workspace membership for verified email domains
- Personal Workspaces - Every user gets their own workspace on signup
- Role-Based Access Control - Permission enforcement at API endpoint level
- Middleware Context Injection - Workspace context validated on every request
Workspace Roles & Permissions
Workspace roles form a hierarchical system where each level inherits permissions from the level below. This four-tier structure covers most organizational needs from external collaborators (Guest) to full workspace control (Owner).
Guest (0) - Read-Only Collaborator
- View workspace details and members
- View groups and group members
- Read-only access to workspace resources
- Cannot modify anything or invite members
Member (1) - Standard User
- Full read/write access to workspace resources
- Create and manage own content
- Collaborate with other members
- Cannot modify workspace settings or manage members
Admin (2) - Manager
- Modify workspace settings (name, description, logo)
- Invite new members with Guest, Member, or Admin roles
- Manage member roles (update, remove members with role ≤ Member)
- Create and manage groups and group membership
- Cannot delete workspace or manage Owners
Owner (3) - Full Control
- Delete workspace
- Promote members to Owner
- Remove any member including other Owners
- Full control over all workspace operations
- Cannot remove self if last Owner (protection mechanism)
Workspace Permission Matrix
| Action | Guest | Member | Admin | Owner |
|---|---|---|---|---|
| View workspace data | Yes | Yes | Yes | Yes |
| Create/edit resources | No | Yes | Yes | Yes |
| Delete own resources | No | Yes | Yes | Yes |
| Manage all resources | No | No | Yes | Yes |
| Modify workspace settings | No | No | Yes | Yes |
| Invite members | No | No | Yes | Yes |
| Manage member roles | No | No | Yes | Yes |
| Create/manage groups | No | No | Yes | Yes |
| Manage billing | No | No | No | Yes |
| Delete workspace | No | No | No | Yes |
Group-Level Permissions
Groups operate with their own permission system independent of workspace roles. This enables delegation where group Leads can manage their groups without requiring workspace Admin permissions, allowing distributed management in larger organizations.
Group Roles:
- Viewer (0) - View group content only
- Member (1) - Create and edit content within group
- Lead (2) - Manage group settings and membership
Group Permission Policies:
- View Content - Anyone in group can view
- Modify Content - Member+ can create/edit (blocks Viewers and workspace Guests)
- Manage Settings - Admin+ at workspace level or Lead at group level
- Add/Remove Members - Member+ with group Lead role or workspace Admin+
- Delete Group - Workspace Admin+ only
Group Delegation: Group Leads can manage their groups without requiring workspace Admin permissions. This enables distributed management in larger organizations while maintaining workspace-level security boundaries.
Hierarchical Groups
Groups support parent-child relationships for modeling real-world organizational structures. Departments can contain teams, teams can contain projects, enabling flexible organization that mirrors company hierarchy.
Common Use Cases:
- Departments (Engineering, Sales, Marketing)
- Teams (Backend Team, Frontend Team within Engineering)
- Projects (Project A, Project B with assigned teams)
- Access Control (Admin Group, Content Editors Group)
Groups can be nested up to a configurable depth (default: 3-5 levels). Child groups are orphaned (parent set to null) when parent group is deleted, preserving the child groups and their members.
Data Isolation & Security
Multi-tenancy security is enforced at three layers: repository queries, middleware validation, and database constraints. This defense-in-depth approach ensures no business logic can accidentally leak data across workspace boundaries.
Repository Layer: Every repository method automatically filters by workspace context. The workspace ID is extracted from the authenticated user's JWT token and injected via middleware.
1public async Task<List<TaskItem>> GetTasks()2{3 var workspaceId = await _currentUserService.GetWorkspaceId();45 return await _dbContext.TaskItems6 .Where(t => t.WorkspaceId == workspaceId)7 .Include(t => t.Group)8 .ToListAsync();9}
Middleware Layer:
WorkspaceContextMiddleware validates workspace membership on every request:
- Extracts workspace ID from route parameter
- Validates user membership in workspace
- Retrieves user's workspace role
- Populates request-scoped
WorkspaceContextService - Returns 403 Forbidden if user is not a member
Database Layer: Foreign key constraints prevent cross-workspace data access. All tenant-scoped entities have WorkspaceId foreign keys with cascading deletes.
Configuration
Multi-tenancy behavior is fully configurable through feature flags, workspace settings, and permission policies. Enable or disable specific functionality, set organizational limits, and define role requirements for operations.
Feature Flags enable specific functionality:
- Enable/disable multi-tenancy system entirely
- Toggle workspace creation and management
- Enable hierarchical group organization
- Control personal workspace creation
- Enable nested groups for complex hierarchies
- Domain verification for automatic membership
- Email-based invitations
- Automatic group assignment on invite
Workspace Settings define limits and behavior:
- Single-tenant or multi-tenant mode
- Automatic personal workspace creation on signup
- Group visibility (all groups or assigned groups only)
- Default limits (workspaces per user, members per workspace, groups per workspace, nesting depth)
Permission Policies define role requirements:
- Workspace-level permissions (minimum role required for operations)
- Group-level permissions (minimum group role required)
- Feature-specific permissions (feature flag + plan tier requirements)
Billing Integration
The multi-tenancy system adapts to your billing model, supporting both B2B (workspace subscriptions) and B2C (user subscriptions) modes. Workspace creation and member management behave differently based on your chosen model.
B2B Mode (Workspace Billing):
- Each workspace has separate subscription
- Unlimited workspace creation
- Workspace Owner manages billing
- Seat-based pricing tracks workspace members
B2C Mode (User Billing):
- User subscription controls workspace creation
- Requires WorkspaceManagement feature in plan
- Free users can join workspaces but cannot create
- User subscription determines workspace features
For detailed configuration, invitation workflows, and advanced scenarios like cross-workspace reporting and domain verification, see the multi-tenancy documentation.
Email System
Multi-provider email system supports Resend, SendGrid, SMTP, and local filesystem for development. Template-based emails with layout inheritance keep your brand consistent. Queued sending via background jobs ensures application responsiveness. Provider abstraction makes switching vendors trivial.
Email Provider Support
Start with Resend for development. Switch to SendGrid for better deliverability. Move to AWS SES for cost savings. The provider abstraction makes vendors interchangeable. Your email templates stay the same; only the configuration changes.
- Multi-Provider Email Support - Choose between Resend, SendGrid, SMTP, or filesystem
- Resend Integration - Modern email API with great developer experience
- SendGrid Integration - Enterprise-grade email delivery
- SMTP Integration - Use any SMTP server including AWS SES, Mailgun
- Local Filesystem Provider - Email capture for development testing
- Template-Based Emails - Handlebars templates with layout inheritance
- Background Job Queuing - Emails sent asynchronously without blocking requests
- Transactional Email Tracking - Track delivery status per email
Email Templates
All emails use a shared layout with header, footer, and branding. Individual templates inherit the layout and provide content:
- Welcome emails after registration
- Email verification links
- Password reset instructions
- Magic link authentication
- Two-factor authentication codes
- Workspace invitations
- Subscription receipts
- Payment failure notifications
- Account deletion confirmations
Configuration
Email system supports multiple providers with simple configuration switching.
Supported Providers:
- Resend - Modern email API with great developer experience
- SendGrid - Enterprise-grade email delivery
- SMTP - Use any SMTP server (AWS SES, Mailgun, etc.)
- Local Filesystem - Capture emails for development testing
Provider Configuration requires:
- API keys or SMTP credentials
- Sender email address and display name
- Provider-specific settings (host, port, SSL for SMTP)
Switch providers by changing a single configuration value.
Template Example
Templates use Handlebars syntax for variable substitution:
1<h1>Welcome to {{AppName}}, {{FirstName}}!</h1>23<p>Your account has been created successfully. Click the button below to verify your email address:</p>45<a href="{{VerificationUrl}}" class="button">Verify Email</a>67<p>This link expires in {{ExpiryMinutes}} minutes.</p>
For creating custom email templates, configuring DKIM/SPF records, and monitoring deliverability, see the email documentation.
Background Jobs
Hangfire manages scheduled jobs, queued tasks, and automatic retries with persistent storage in PostgreSQL. Jobs survive application restarts. Failed jobs retry automatically with exponential backoff. The dashboard provides monitoring and manual job triggering.
Jobs That Actually Run
Background jobs aren't optional infrastructure you figure out later. They're essential for webhooks, emails, CRM sync, and data cleanup. Hangfire handles job persistence, retries, and monitoring out of the box. Jobs survive server restarts and retry failed operations automatically with exponential backoff.
- Recurring Scheduled Jobs - Schedule jobs with cron expressions for regular tasks
- Fire-and-Forget Task Queue - Queue tasks without waiting for completion
- Automatic Retry with Exponential Backoff - Failed jobs retry automatically with backoff
- Hangfire Dashboard Monitoring - Monitor job status, view failures, manually trigger jobs
- Persistent Job Storage - Jobs survive application restarts in PostgreSQL
- Production Usage Throughout - Stripe webhooks, email sending, HubSpot CRM sync
Configuration
Background jobs are controlled through feature flags and timing settings.
Feature Flags enable specific jobs:
- Token cleanup (expired refresh tokens, magic links, password reset)
- Account creation confirmation cleanup
- Audit log cleanup and archival
- Account deletion processing
- Billing sync with Stripe
- Abandoned checkout recovery
Job Settings control timing:
- Job execution intervals (hourly, daily, weekly)
- Cleanup thresholds (how old items must be before removal)
- Retention periods (audit logs, checkout sessions)
- Reminder schedules (abandoned checkouts, account deletion)
Pre-Configured Jobs
Here's a list of all pre-configured jobs:
- Token Cleanup - Remove expired authentication tokens (daily)
- Account Creation Confirmation Cleanup - Remove stale account confirmations (daily)
- Audit Log Cleanup - Archive old audit log entries (weekly)
- Account Deletion Cleanup - Process scheduled account deletions (daily)
- Billing Sync - Synchronize subscription data with Stripe (every 12 hours)
- Abandoned Checkout Cleanup - Clean up expired checkout sessions and send reminder emails (daily)
Implementation Examples
1// Schedule recurring job2RecurringJob.AddOrUpdate<ITokenCleanupService>(3 "token-cleanup",4 service => service.CleanupExpiredTokensAsync(),5 Cron.Daily6);78// Fire-and-forget task9BackgroundJob.Enqueue<IEmailService>(10 service => service.SendWelcomeEmailAsync(userId)11);1213// Process Stripe webhook asynchronously14[HttpPost("stripe/webhook")]15public async Task<ActionResult> StripeWebhook()16{17 var evt = await _stripeWebhookService.ConstructEventAsync(Request);1819 BackgroundJob.Enqueue<IStripeWebhookHandler>(20 handler => handler.HandleEventAsync(evt.Id, evt.Type)21 );2223 return Ok();24}
For configuring the Hangfire dashboard, setting up authorization, and monitoring job performance, see the background jobs documentation.
Storage System
Switch between storage providers with a configuration change. Supports S3-compatible services (AWS S3, Cloudflare R2), Azure Blob Storage, and local filesystem for development. Quota enforcement per workspace ensures fair usage. Automatic cleanup removes orphaned files.
Storage Provider Support
Start with Cloudflare R2 for cost savings. Move to AWS S3 for global distribution. Switch to Azure if you're already in that ecosystem. The storage abstraction layer makes providers interchangeable. Your application code stays the same; only the configuration changes.
- Multi-Provider Cloud Storage Support - Choose between AWS S3, R2, Azure, or local filesystem
- S3-Compatible Storage Integration - AWS S3, Cloudflare R2
- Azure Blob Storage Integration - Native Azure cloud integration
- Local Filesystem for Development - Simple setup for local development
- Per-Workspace Storage Quota Enforcement - Storage limits based on subscription plan
- Automatic Orphaned File Cleanup - Remove orphaned files via background jobs
- Integrated Throughout Application - User avatars, workspace logos, file uploads
Supported Providers
| Provider | Configuration | Best For |
|---|---|---|
| Cloudflare R2 | S3-compatible | Cost-effective, zero egress fees |
| AWS S3 | Native SDK | Production-grade, global infrastructure |
| Azure Blob | Native SDK | Azure ecosystem integration |
| Local Filesystem | File path | Development, testing |
Configuration
Storage system supports multiple cloud providers and local filesystem for development.
Supported Providers:
- Cloudflare R2 (S3-compatible) - Cost-effective, zero egress fees
- AWS S3 - Production-grade, global infrastructure
- Azure Blob Storage - Native Azure cloud integration
- Local Filesystem - Development and testing
Storage Settings control behavior:
- Active provider selection
- File type restrictions (images, documents)
- Maximum file size limits
- Provider-specific credentials and bucket/container configuration
- Public access settings
- Signed URL expiry times
- Custom domains for CDN integration
Usage in Application Code
1public async Task<string> UploadAvatarAsync(Guid userId, IFormFile file)2{3 var fileName = $"avatars/{userId}/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";45 await using var stream = file.OpenReadStream();6 var url = await _storageService.UploadAsync(fileName, stream, file.ContentType);78 await _userRepository.UpdateAvatarUrl(userId, url);910 return url;11}
For details on configuring CDN integration, handling large file uploads, and implementing file previews, check the storage documentation.
Resource Tracking & Quota Enforcement
Cached consumption tracking with O(1) quota validation prevents expensive real-time aggregations. Track storage, API calls, email sends, or custom resource types with automatic plan limit enforcement. Periodic resets support daily, monthly, or yearly quotas. Extensible system handles any consumption-based feature.
Performance-Optimized Tracking
Replace expensive SUM aggregations with cached values. Validate quota in 1-5ms instead of 500-2000ms for workspaces with hundreds of thousands of records.
- Cached Consumption Tracking - Pre-calculated usage values for instant validation
- Plan Limit Enforcement - Block operations before they exceed quota
- Multi-Scope Support - Track user-level and workspace-level resources independently
- Automatic Periodic Resets - Daily, monthly, yearly, or manual reset intervals
- Transactional Updates - Usage increments/decrements within same transaction as operation
- Extensible Resource Types - Storage, API calls, emails, or custom types
- Reconciliation Jobs - Automatic drift detection and correction
How It Works
Before Operation:
1// Validate quota before file upload (1-5ms lookup)2await _resourceTracking.ValidateResourceLimitAsync(3 ResourceType.Storage,4 amountToAdd: file.Length,5 workspaceId: currentWorkspaceId6);
After Operation:
1// Increment cached usage after successful upload2await _resourceTracking.IncrementUsageAsync(3 ResourceType.Storage,4 amount: file.Length,5 itemCount: 1,6 workspaceId: currentWorkspaceId7);
Performance Comparison:
- Without caching: SUM query over 100,000 files = 500-2000ms
- With caching: Indexed lookup = 1-5ms
- 100-2000x faster validation
Supported Resource Types
Built-in Types:
- Storage - Track bytes consumed, file counts, enforce workspace quota
- Emails - Daily email sending limits with midnight resets
- API Calls - Monthly API request quotas
- Tokens - AI token consumption tracking
- Credits - Prepaid credit balances with manual resets
Custom Types:
- Custom1, Custom2, Custom3 slots for new features
- Configure via appsettings without code changes
- Examples: AI generations, video transcoding minutes, report exports
Configuration
Resource tracking behavior is configured per resource type with flexible reset intervals and enforcement policies.
Resource Configuration defines tracking behavior:
- Enable/disable tracking per resource type
- Reset interval (Never, Daily, Monthly, Yearly, Manual)
- Track item count in addition to total amount
- Enforce limits (block when exceeded) or track only
- Link to plan feature key for limit values
- Metadata for display (unit, description)
Reset Strategies:
- Never - Cumulative resources like storage (never resets)
- Daily - Email sending limits (resets at midnight UTC)
- Monthly - API call quotas (resets on 1st of month)
- Manual - Prepaid credits (only reset via explicit API call)
Billing Integration
Resource limits are read from subscription plan features. Plan upgrades immediately increase available quota without cache invalidation.
Example:
- Workspace on Pro plan (100GB storage limit) at 95GB usage
- Upgrade to Business plan (1TB storage limit)
- Next validation reads 1TB from new plan
- Workspace immediately has 929GB available
For reconciliation jobs, custom resource types, and performance tuning, see the resource tracking documentation.
CRM Integration
Keep your CRM in sync with your SaaS automatically. Contacts are synced from user registrations, companies from workspaces, and deals from subscriptions. Background jobs handle synchronization asynchronously. Lifecycle stages track the customer journey from lead to paying customer.
Your CRM Stays Current Without Manual Work
User signs up, contact appears in HubSpot. Workspace created, company record added. Subscription starts, deal moves to closed-won. The sync happens in background jobs so your application stays fast while your sales team gets real-time visibility into customer activity.
- Automatic Bidirectional CRM Sync - No manual data entry required
- Contact Sync from User Registration - User registrations automatically create CRM contacts
- Company Sync from Workspaces - Workspaces automatically become CRM companies
- Deal Sync from Stripe Subscriptions - Subscriptions automatically create CRM deals
- Asynchronous Background Processing - Sync doesn't slow down application performance
- Lifecycle Stage Tracking - Automatic stage progression (Lead to Customer)
- Deal Pipeline Management - Track subscription journey through sales stages
- Extensible Provider Pattern - Easy to add Salesforce, Pipedrive, or custom CRM
How Data Flows
When a user registers, the system creates their account in the database and queues a background job to sync their information to HubSpot as a contact. If they belong to a workspace, the contact is automatically associated with the corresponding company record in HubSpot.
When a subscription is created or updated, the system saves the changes to the database and queues another background job. This job creates or updates the deal record in HubSpot, setting the deal stage based on the subscription status (trial, active, cancelled, etc.). All synchronization happens asynchronously so the application remains fast and responsive.
Lifecycle Stages
- Lead - User registered, no subscription
- Marketing Qualified Lead - Email verified
- Sales Qualified Lead - Started trial or checkout
- Opportunity - In trial period
- Customer - Active paid subscription
- Evangelist - Long-term customer
Configuration
CRM integration is configurable for automatic synchronization with your sales pipeline.
CRM Settings control sync behavior:
- Enable/disable CRM integration
- Choose provider (currently HubSpot, extensible for others)
- Automatic sync on record creation
- Automatic sync on record updates
- API credentials for provider access
- Deal stage mapping (lifecycle stages to CRM pipeline stages)
Learn how to add custom fields, implement bidirectional sync, and integrate other CRM providers in the CRM documentation.
Technical Architecture Deep Dive
The backend uses a feature-based modular architecture where each feature is self-contained with its own controllers, services, repositories, entities, DTOs, and configuration. This pattern keeps related code together, makes feature boundaries clear, and enables teams to work on different modules without conflicts. The frontend follows a matching feature-based organization, with React 19 and TypeScript providing full type safety from API to UI.
Backend Architecture
Each feature module is completely self-contained, following a consistent vertical slice architecture. Controllers handle HTTP requests, services contain business logic, repositories manage data access, entities define database models, and DTOs shape API contracts. Configuration files keep settings isolated per feature.
Example Features:
Auth/- Authentication and authorizationBilling/- Stripe subscription managementMultiTenancy/- Workspaces and role-based accessStorage/- Multi-provider file storageCRM/- HubSpot integrationEmail/- Multi-provider email systemResources/- Resource tracking and quotasCommon/- Shared infrastructure
Clean Architecture Pattern
The codebase follows a layered architecture with clear separation of concerns:
Controller Layer - HTTP request handling, validation, authentication
- Accepts DTOs (Data Transfer Objects)
- Returns strongly-typed responses
- Applies authorization policies
- Minimal business logic
Service Layer - Business rules and workflow orchestration
- Coordinates repository operations
- Implements business logic
- Handles cross-feature coordination
- Validates business rules
Repository Layer - Data access and query composition
- EF Core repositories
- Query building and filtering
- Workspace/tenant scoping
- Database operations
Database Layer - PostgreSQL with Entity Framework Core
- 30+ database tables
- Foreign key constraints
- Indexes for performance
- Migrations for schema changes
Dependency Injection Throughout
Every feature exposes an extension method for clean service registration:
1// Program.cs - Clean feature registration2builder.Services.AddAuthServices(builder.Configuration);3builder.Services.AddMultiTenancyServices(builder.Configuration);4builder.Services.AddBillingServices(builder.Configuration);5builder.Services.AddStorageServices(builder.Configuration);6builder.Services.AddCRMServices(builder.Configuration);7builder.Services.AddEmailServices(builder.Configuration);8builder.Services.AddResourceTracking(builder.Configuration);
Key Architectural Patterns
Primary Constructor Pattern (C# 14)
Services use primary constructors to eliminate boilerplate:
1public class AuthService(2 IAuthRepository authRepository,3 IEmailService emailService,4 IAuditLogService auditLogService,5 ILogger<AuthService> logger) : IAuthService6{7 // No field declarations needed - parameters captured automatically89 public async Task<SignupResponse> Signup(SignupRequest request)10 {11 logger.LogInformation("Processing signup for {Email}", request.Email);12 var user = await authRepository.CreateUser(new User { Email = request.Email });13 await emailService.SendWelcomeEmail(user.Email);14 return new SignupResponse { UserId = user.Id };15 }16}
Feature Flags System
Enable or disable modules via configuration without code changes:
1{2 "FeatureManagement": {3 "Authentication__MagicLink": true,4 "Authentication__OAuth__Google": true,5 "Security__TwoFactorAuth": true,6 "Billing__InAppManagement": true,7 "MultiTenancy__Groups": true8 }9}
OpenAPI/Swagger Documentation Automatic TypeScript client generation from backend API:
- Backend generates OpenAPI spec via NSwag
- Frontend consumes strongly-typed API client
- Full type safety from API to UI
- No manual client code writing
Security Audit Logging Comprehensive tracking of sensitive operations:
- Authentication events (signin, signout, 2FA changes)
- Workspace operations (creation, member changes, role updates)
- Billing events (subscription changes, payment updates)
- Configurable retention per user
Middleware Pipeline Centralized request processing:
- Global exception handling with correlation IDs
- Security headers (HSTS, CSP, X-Frame-Options)
- Workspace context extraction and validation
- Authentication and authorization
- Rate limiting
- CORS policy enforcement
Frontend Architecture
The frontend is built with modern React patterns optimized for reliability and developer experience.
Core Technologies:
- React 19 with Suspense for declarative loading
- TypeScript 5.8 with strict mode for type safety
- Vite 7 for fast development and optimized builds
- React Router 7 for client-side routing
- TanStack Query v5 for server state management
- Zustand 5 for UI state management
Feature-Based Organization
The frontend mirrors the backend's feature structure for consistency, with feature modules in common/ organized by domain (auth, billing, multitenancy, storage, etc.). This matching structure makes it easy to find related code across the stack.
State Management Strategy
TanStack Query (Server State) Handles all API data with automatic caching and refetching:
- User profiles, workspaces, projects
- Automatic background refetching
- Optimistic updates
- Query invalidation
- Suspense integration
Zustand (UI State) Manages application state with localStorage persistence:
- Authentication tokens and status
- Active workspace selection
- Billing paywall state
- Dialog and modal state
- Error boundaries
Route Guards & Protection
Multi-layer security with progressive enhancement:
Layer 1: Authentication Guards
- Protect all private routes
- Automatic token refresh
- Redirect to signin on expiry
Layer 2: Permission Guards
- Workspace role requirements
- Feature access control
- Graceful fallbacks
Layer 3: Billing Guards
- Subscription status checks
- Feature-level restrictions
- Paywall display
Layer 4: Group Guards
- Group membership verification
- Group role requirements
Type Safety From API to UI
Full type safety across the entire stack:
Backend:
- C# 14 with strict nullability
- DTOs define API contracts
- OpenAPI spec generation
Generated Client:
- TypeScript interfaces from OpenAPI
- Axios client with types
- Request/response validation
Frontend:
- Strict TypeScript compilation
- Zod schema validation
- React Hook Form integration
Database Architecture
The application uses a single unified PostgreSQL with Entity Framework Core database with clear schema organization:
Total Tables: 30
Authentication Tables (14):
- Users, RefreshTokens, MagicLinkTokens
- EmailVerificationTokens, PasswordResetTokens
- UserTwoFactorSettings, TwoFactorTempTokens
- PasswordHistories, Disable2FATokens
- AccountCreationConfirmations, AccountDeletionTokens
- EmailChangeTokens, DataExportTokens
- SecurityAuditLogs
Multi-Tenancy Tables (7):
- Workspaces, WorkspaceMembers, WorkspaceInvitations
- Groups, GroupMembers, GroupAccessRequests
- VerifiedDomains
Billing Tables (6):
- SubscriptionPlans, Subscriptions
- ProcessedWebhookEvents, CheckoutSessions
- SubscriptionCancellations, RetentionOffers
Storage & Resources (2):
- StorageFiles
- ResourceUsages
Demo Feature (1):
- TaskItems (removable demo)
Multi-Tenancy Enforcement
Data isolation happens at the repository layer with automatic workspace filtering:
1// Repository automatically filters by workspace context2public async Task<List<TaskItem>> GetTasks()3{4 var workspaceId = await _currentUserService.GetWorkspaceId();56 return await _dbContext.TaskItems7 .Where(t => t.WorkspaceId == workspaceId)8 .Include(t => t.Group)9 .ToListAsync();10}
Multi-Layer Security:
1. Middleware Layer: Workspace context middleware validates membership on every request:
- Extracts workspace ID from route
- Verifies user is workspace member
- Returns 403 if user not member
- Populates workspace context for services
2. Repository Layer: Every query automatically filters by workspace:
- Workspace ID from current user context
- EF Core query filters enforce scoping
- No business logic can bypass filters
3. Database Layer: Foreign key constraints prevent cross-workspace access:
- All entities have WorkspaceId foreign key
- Cascading deletes for data cleanup
- Indexes on WorkspaceId for performance
Entity Relationships
| Entity | Relationship | Related Entity | Description |
|---|---|---|---|
| User | Many-to-Many | Workspace | Users belong to multiple workspaces; workspaces have multiple users with assigned roles |
| Workspace | One-to-Many | Group | Each workspace can contain multiple hierarchical groups for team organization |
| Workspace | One-to-One | Subscription | In B2B mode, each workspace has one subscription (B2C mode: users have subscriptions) |
| Workspace | One-to-Many | StorageFile | Files are scoped to workspaces with quota enforcement based on plan limits |
| User | One-to-Many | RefreshToken | Each user can have multiple active sessions tracked via refresh tokens |
| Workspace | One-to-Many | Invitation | Workspaces can have multiple pending invitations with expiry and revocation |
| Group | One-to-Many | GroupMember | Groups contain members with specific roles (Viewer, Member, Lead) |
| User | One-to-Many | PasswordHistory | System tracks password history to prevent reuse of recent passwords |
| Workspace | One-to-Many | ResourceUsage | Cached consumption tracking for storage, API calls, and other quotas |
For detailed setup instructions, migration strategies, and deployment configurations, see the comprehensive documentation.