Back to Django episodes

Django · Episode 5

Django SaaS Without the Fantasy: Teams, Billing, Tenants, and the Hard Parts

A practical Django podcast episode about building SaaS products with teams, subscriptions, permissions, invoices, onboarding, multi-tenancy, PostgreSQL, background jobs, and production discipline.

HostGabriela B.Lead Software Engineer - Web, WordPress and Project Management

GuestLeo Grant — Founder and Django SaaS Architect — TenantKit Labs

Django SaaS Without the Fantasy: Teams, Billing, Tenants, and the Hard Parts

#5: Django SaaS Without the Fantasy: Teams, Billing, Tenants, and the Hard Parts

Original editorial from Softaims, published in a podcast-style layout—details, show notes, timestamps, and transcript—so the guidance is easy to scan and reference. The host is a developer from our verified network with experience in this stack; the full text is reviewed and edited for accuracy and clarity before it goes live.

Details

This episode focuses on Django for SaaS products: accounts, teams, billing, permissions, onboarding, and multi-tenant data design.

The conversation explains why SaaS complexity usually starts after login: invitations, roles, invoices, trials, cancellation, support, and audit trails.

The guest discusses tenant models, PostgreSQL, database routing, background tasks, webhooks, and admin workflows.

The episode avoids fake simplicity: it explains when a shared-database tenant model is enough and when separate databases or schemas may be justified.

The core message is that Django is strong for SaaS when teams model business rules clearly instead of hiding them behind generic starter templates.

Show notes

  • Why Django is a strong fit for many B2B SaaS products
  • The difference between users, accounts, teams, organizations, and tenants
  • Shared database multi-tenancy versus separate database models
  • PostgreSQL and relational data modeling for SaaS
  • Django database routers and when multiple databases matter
  • Billing workflows: trials, subscriptions, invoices, cancellation, and failed payments
  • Webhook handling and idempotency
  • Team invitations, roles, permissions, and ownership transfer
  • Django admin as a support and operations surface
  • Background jobs for billing sync, emails, reports, and cleanup
  • Audit logs and customer support receipts
  • Common SaaS mistakes Django teams should avoid

Timestamps

  • 0:00Cold open: SaaS gets hard after login
  • 2:30Why Django fits practical SaaS products
  • 5:00Users, teams, organizations, and tenants
  • 8:00The first multi-tenancy decision
  • 11:00Shared database versus separate database
  • 14:00PostgreSQL as the product truth
  • 17:00Permissions: member, admin, owner, and support
  • 20:00Invitations and onboarding
  • 23:00Billing is not just checkout
  • 26:00Webhooks and idempotency
  • 29:00Trials, upgrades, downgrades, and cancellation
  • 32:00Background jobs for SaaS operations
  • 35:00Django admin as the support cockpit
  • 38:00Audit logs and customer receipts
  • 41:00Testing SaaS edge cases
  • 44:00Security and tenant isolation
  • 47:00The stack Leo would choose today
  • 50:00Mistakes SaaS founders make with Django
  • 53:00Closing: SaaS is workflow software
  • 55:00End

Transcript

[0:00]Gabriela: Welcome back to Builder Notes. Today we are staying with Django, but this episode has a different angle: SaaS. Not the fantasy version of SaaS where you install a starter kit, add Stripe, ship a pricing page, and suddenly have a business. We are talking about the real version: teams, billing, invitations, roles, tenants, failed payments, invoices, support access, cancellation, audit logs, and the awkward edge cases that appear the first time a real company uses your product.

[0:58]Gabriela: SaaS usually does not get hard at the landing page. It gets hard after login. A user creates a workspace. Then they invite a teammate. Then the teammate uses a different email. Then the founder leaves the company. Then the card fails. Then the account owner wants invoices sent to finance. Then support needs to inspect the account without accidentally changing data. This is where the architecture becomes real.

[1:50]Gabriela: Our guest is Leo Grant, founder and Django SaaS architect at TenantKit Labs. Leo has helped build subscription products, internal platforms, analytics tools, and B2B SaaS apps using Django and PostgreSQL. Leo, welcome.

[2:10]Leo Grant: Thanks for having me. I like that you started after login, because that is where most SaaS templates stop being honest. They show you authentication, pricing cards, and maybe a checkout button. But the actual product has to answer questions like: who owns this workspace, who can invite users, what happens when billing fails, what data belongs to which tenant, and what can support see? Django is very good at this kind of business logic, but only if you model it directly.

[2:30]Gabriela: Why does Django fit practical SaaS products?

[2:38]Leo Grant: Because most SaaS products are not just APIs or dashboards. They are permissioned business systems. You need users, organizations, roles, subscriptions, invoices, settings, emails, admin tools, reports, and background jobs. Django gives you strong primitives for that: models, migrations, authentication, admin, forms, templates, middleware, and a mature ecosystem. The value is not that Django magically gives you SaaS. The value is that it gives you a coherent place to model the business.

[3:42]Gabriela: So Django is not replacing product thinking.

[3:46]Leo Grant: Exactly. Django helps when your thinking is clear. It does not save you from vague concepts. If your team cannot define the difference between a user, a customer, an organization, a billing account, and a tenant, the code will become confusing no matter what framework you use.

[4:30]Gabriela: That is a good warning. SaaS language gets sloppy fast.

[4:35]Leo Grant: Very fast. People say account when they mean user. They say tenant when they mean organization. They say customer when they mean billing entity. Those words affect database design, permissions, and support workflows. Getting the language right early is not cosmetic. It prevents expensive confusion.

[5:00]Gabriela: Let us define the core nouns. What is the difference between users, teams, organizations, and tenants?

[5:10]Leo Grant: A user is a human who logs in. An organization is usually the company, workspace, school, clinic, agency, or group that owns data. A team may be a subdivision inside an organization, depending on the product. A tenant is the isolation boundary: the scope that decides which data belongs together and which data must never leak across customers. In many B2B SaaS apps, organization and tenant are the same thing. But not always.

[6:05]Gabriela: Give me an example where they are not the same.

[6:09]Leo Grant: Imagine an agency using your product to manage clients. The agency is the paying organization, but each client might need isolated projects, reports, and permissions. Or imagine an enterprise with departments that need separate billing or data boundaries. If you flatten all of that into one account model, you may regret it later.

[7:00]Gabriela: What is the first practical step?

[7:04]Leo Grant: Write down the nouns before writing models. User, organization, membership, role, invitation, subscription, invoice, plan, tenant, project, audit event. Then ask which nouns are product concepts and which are billing concepts. That one exercise prevents a lot of bad schema design.

[8:00]Gabriela: Let us talk about multi-tenancy. What is the first decision?

[8:07]Leo Grant: The first decision is the isolation model. Are all tenants in one shared database with tenant IDs on rows? Are tenants separated by schema? Are they separated by database? Or are only some high-value tenants separated? Most early SaaS products should start with a shared database and strong tenant scoping unless they have regulatory, scale, or enterprise reasons not to.

[9:10]Gabriela: Why start shared?

[9:13]Leo Grant: Because it is operationally simpler. One database, one migration path, easier reporting, easier support, simpler deployment. But shared database does not mean casual security. Every tenant-owned table needs clear organization or tenant ownership. Querysets must be scoped. Tests must prove users cannot cross tenant boundaries.

[10:05]Gabriela: What is the danger?

[10:08]Leo Grant: The danger is forgetting a filter. If a view loads a project by ID without checking the organization, a user may access another tenant’s data. That is the classic SaaS security bug. Shared databases are fine only when tenant scoping is treated as a first-class rule.

[11:00]Gabriela: When do separate databases make sense?

[11:06]Leo Grant: Separate databases can make sense for enterprise isolation, data residency, compliance, noisy-neighbor problems, very large tenants, or custom backup and restore requirements. Django supports multiple databases through database routing, so the framework gives you a path. But multiple databases increase operational complexity. Migrations, reporting, support tooling, backups, analytics, and debugging all get harder.

[12:18]Gabriela: So separate database is not automatically more mature.

[12:22]Leo Grant: Correct. It can be more isolated, but it is also more complex. Mature architecture is not always more separated. Mature architecture matches the risk. If you have twenty small customers and no regulatory pressure, separate databases may be theater. If you have bank customers with contractual isolation requirements, it may be necessary.

[13:20]Gabriela: What about schema-per-tenant?

[13:24]Leo Grant: It sits between shared database and separate database. It can provide cleaner separation than tenant IDs, but it still adds migration and tooling complexity. I would not recommend it casually. Teams should understand the operational cost before choosing it.

[14:00]Gabriela: Let us talk about PostgreSQL as the product truth.

[14:06]Leo Grant: For Django SaaS, PostgreSQL is often the best default because SaaS data is relational. Users belong to organizations through memberships. Organizations have subscriptions. Subscriptions have invoices. Projects belong to organizations. Audit events refer to actors and targets. These are relationships with rules. PostgreSQL is very good at enforcing rules close to the data.

[15:05]Gabriela: What rules should be enforced in the database?

[15:09]Leo Grant: Uniqueness where it matters: one active membership per user per organization, one active owner if your product requires it, unique invitation tokens, unique external billing IDs. Foreign keys for ownership. Not-null constraints for required state. Check constraints for valid values when appropriate. The app should be friendly, but the database should be stubborn.

[16:10]Gabriela: The database should be stubborn. That is a good line.

[16:15]Leo Grant: It saves you. If the database allows nonsense, one day the app will create nonsense. Then support has to explain nonsense to a customer.

[17:00]Gabriela: Permissions are where SaaS apps quietly become risky. What roles do you usually start with?

[17:08]Leo Grant: Usually member, admin, and owner. Member can use the product. Admin can manage settings and invite people. Owner can manage billing, transfer ownership, and close the account. Sometimes billing admin is separate. Sometimes viewer is needed. The mistake is adding ten roles before you understand the workflow.

[18:05]Gabriela: Where does support fit?

[18:09]Leo Grant: Support is special. Staff users may need to inspect accounts, but their access should be logged, limited, and sometimes approved. Support access is not the same as customer membership. Do not fake it by adding staff users to customer organizations unless that is truly the model. Build support tooling deliberately.

[19:05]Gabriela: What is the test every SaaS app needs?

[19:09]Leo Grant: User A from Organization A must not access Organization B’s object by guessing an ID. Write that test for every sensitive model. Projects, invoices, reports, files, API keys, team members, everything. If you only test happy paths, you will miss the bug that matters most.

[20:00]Gabriela: Invitations sound simple. Why are they not?

[20:06]Leo Grant: Because real people use multiple emails, change jobs, forward invites, let tokens expire, and join the wrong workspace. You need invitation status, expiration, role, inviter, target organization, accepted timestamp, and sometimes domain restrictions. You also need to decide whether an invited email must match the signup email.

[21:05]Gabriela: What is a clean invitation flow?

[21:09]Leo Grant: An admin invites an email to an organization with a role. The system creates an invitation record with a token and expiration. The recipient accepts. If they already have an account, they join. If not, they create one. The acceptance step creates a membership. The invitation is marked accepted. Every step should be auditable.

[22:10]Gabriela: And onboarding?

[22:13]Leo Grant: Onboarding should reflect the account state. Has the organization been created? Has billing started? Has the first project been created? Has a teammate been invited? Has required setup been completed? Do not hard-code onboarding as a tour. Model it as product state.

[23:00]Gabriela: Billing is not just checkout. Explain.

[23:06]Leo Grant: Checkout is one moment. Billing is a lifecycle. Trial starts, trial ends, subscription activates, payment fails, invoice is issued, plan changes, seats increase, coupon applies, tax changes, card expires, customer cancels, customer reactivates, invoice is disputed. If your app only handles successful checkout, it is not billing-ready.

[24:05]Gabriela: What should Django own versus the payment provider?

[24:10]Leo Grant: The payment provider should own payment processing, invoices, taxes if configured, and external billing records. Django should own your product access state and store references to external billing IDs. The dangerous middle is when your app assumes payment state without syncing carefully.

[25:08]Gabriela: So subscription status is product-critical.

[25:12]Leo Grant: Yes. It decides access. If it is wrong, users either lose access unfairly or keep access without paying. You need clear states: trialing, active, past due, canceled, paused, maybe grace period. And you need to know which states allow product access.

[26:00]Gabriela: Webhooks are where billing bugs often happen. What should teams know?

[26:08]Leo Grant: Webhooks are external facts arriving asynchronously. They can arrive out of order, more than once, or after your own request flow already changed something. So webhook handlers must be idempotent. Processing the same event twice should not create two subscriptions, two invoices, or two emails.

[27:05]Gabriela: How do you make webhooks idempotent?

[27:09]Leo Grant: Store the external event ID. Check whether it has been processed. Use database transactions. Update local records based on external IDs, not guesses. Log the result. If processing fails, make retry safe. A webhook handler is not a place for clever shortcuts.

[28:10]Gabriela: What should the admin show for billing?

[28:14]Leo Grant: Organization, subscription status, plan, seat count, billing email, external customer ID, latest invoice, payment failure state, webhook history, and important timestamps. Support should be able to answer: why does this customer have or not have access?

[29:00]Gabriela: Trials, upgrades, downgrades, cancellation. Which one is most underestimated?

[29:07]Leo Grant: Downgrades. Upgrades are happy. Downgrades ask hard questions. What happens if the customer has ten users and downgrades to a plan with five seats? What happens to premium features already used? What happens to stored data? Do you lock, delete, archive, or allow read-only access? Those are product decisions before they are billing code.

[30:12]Gabriela: And cancellation?

[30:15]Leo Grant: Cancellation should be honest. Does access stop immediately or at period end? Can the customer export data? Can they reactivate? What emails do they receive? What does support see? If cancellation is unclear, users lose trust quickly.

[31:10]Gabriela: What about free trials?

[31:13]Leo Grant: Free trials are product promises. You need trial start, trial end, reminders, conversion rules, and access behavior after expiration. If the app silently shuts off a workspace without warning, that is bad product design.

[32:00]Gabriela: Where do background jobs fit in Django SaaS?

[32:07]Leo Grant: Everywhere. Sending emails, syncing billing status, generating reports, exporting data, cleaning expired invitations, checking failed payments, sending trial reminders, processing imports, and refreshing analytics. SaaS has a lot of work that should not block a web request.

[33:05]Gabriela: What makes a background job production-ready?

[33:09]Leo Grant: Status, retries, idempotency, logging, and user-visible outcomes where needed. A failed export should not disappear. A trial reminder should not send five times. A billing sync should record what changed. Background jobs need receipts.

[34:05]Gabriela: Django now has a first-party Tasks framework, but production teams still need to think about execution.

[34:12]Leo Grant: Right. Defining a task and operating a task system are related but not identical. You still need workers, monitoring, retries, failure handling, and deployment discipline. The framework can standardize the shape, but the team owns the operations.

[35:00]Gabriela: You call Django admin the support cockpit. Why?

[35:06]Leo Grant: Because support needs a safe, accurate place to understand accounts. They need to see organizations, users, memberships, billing state, invitations, feature flags, recent events, and notes. If the admin is poor, support creates side spreadsheets. Then the product truth splits into multiple places.

[36:05]Gabriela: What makes the admin safe?

[36:09]Leo Grant: Read-only fields for sensitive data, permission-aware actions, clear filters, warnings before destructive changes, links to external billing records, audit trails, and staff roles. Not every staff user should be able to cancel accounts, change owners, or view billing details.

[37:08]Gabriela: So internal UX is not optional.

[37:12]Leo Grant: Exactly. Internal mistakes become customer-facing problems. Good admin design reduces mistakes before they happen.

[38:00]Gabriela: Audit logs and receipts. What should a SaaS app record?

[38:07]Leo Grant: Important events: user invited, role changed, owner transferred, billing plan changed, subscription canceled, API key created, data exported, support accessed account, project deleted, integration connected. The goal is to answer what happened, who did it, and when.

[39:05]Gabriela: What should users see?

[39:09]Leo Grant: For B2B SaaS, many customers appreciate an activity log. It does not need to expose internal implementation details. But showing admins that a teammate changed settings or invited a user builds trust. It also reduces support tickets.

[40:05]Gabriela: What should not go into logs?

[40:08]Leo Grant: Secrets, raw passwords, full payment details, unnecessary personal data, and noisy events nobody will use. Audit logs should be useful and privacy-aware.

[41:00]Gabriela: Testing SaaS edge cases. What should teams test first?

[41:07]Leo Grant: Tenant isolation, permission roles, invitations, billing webhooks, subscription access, ownership transfer, cancellation, and admin actions. The happy path matters, but SaaS breaks at boundaries. Boundary tests are where confidence comes from.

[42:05]Gabriela: Give me a concrete test.

[42:08]Leo Grant: A member should not access billing settings. An admin may invite users but not transfer ownership. An owner can cancel. A support user can view billing status but not change it unless they have a specific permission. These rules should be tests, not tribal knowledge.

[43:05]Gabriela: What about webhooks?

[43:08]Leo Grant: Test duplicate webhook delivery. Test out-of-order events. Test unknown external IDs. Test failed processing and retry. Billing bugs are expensive because they touch money and access.

[44:00]Gabriela: Security and tenant isolation deserve their own section. What is your baseline?

[44:08]Leo Grant: Every tenant-owned query must be scoped. Sensitive actions require permission checks. Staff access is logged. API keys are hashed or stored safely depending on design. Exports are permissioned. File access is tenant-aware. Background jobs carry tenant context safely. And tests prove isolation.

[45:08]Gabriela: What is the mistake with middleware-based tenant scoping?

[45:12]Leo Grant: Thinking middleware alone solves it. Middleware can identify the current tenant, but your data access patterns still matter. Management commands, background jobs, admin actions, and scripts may not go through the same request path. Tenant safety has to exist beyond the request.

[46:10]Gabriela: That is important. SaaS is not only web requests.

[46:14]Leo Grant: Exactly. Some of the riskiest operations happen outside normal requests: imports, exports, webhooks, billing syncs, and support scripts.

[47:00]Gabriela: If you were starting a Django SaaS today, what stack would you choose?

[47:08]Leo Grant: Django on the current stable line, PostgreSQL, server-rendered templates for early product surfaces, small JavaScript or HTMX-style interactions where needed, a task system for background work, a payment provider integration, object storage for files, error tracking, structured logs, and a carefully customized admin. I would add a separate frontend or multiple databases only when the product clearly earns them.

[48:12]Gabriela: What models would you create early?

[48:16]Leo Grant: Organization, Membership, Invitation, Subscription, Plan, InvoiceReference, AuditEvent, maybe FeatureFlag, and whatever core product object the SaaS actually sells. I would avoid making the billing provider’s objects the center of my domain. They are external references, not the whole business model.

[49:12]Gabriela: What would you delay?

[49:15]Leo Grant: Complex enterprise roles, separate tenant databases, custom billing engines, heavy analytics infrastructure, and a separate frontend unless needed. Delay does not mean ignore. It means design so you can add complexity later with evidence.

[50:00]Gabriela: Common mistakes SaaS founders make with Django?

[50:07]Leo Grant: They copy a starter template without understanding it. They mix user and organization concepts. They skip tenant isolation tests. They treat billing as a checkout page. They make support access too powerful. They put business rules in random views. They rely on signals for critical workflows nobody can trace. They ignore cancellation and downgrades until customers ask.

[51:12]Gabriela: Which mistake hurts fastest?

[51:15]Leo Grant: Bad permissions. Billing mistakes are painful, but cross-tenant data leaks are existential. If a customer sees another customer’s data, trust is gone.

[52:00]Gabriela: What advice would you give a solo founder building Django SaaS?

[52:06]Leo Grant: Keep the architecture simple, but make the rules explicit. Start with one database. Model organizations and memberships clearly. Use a payment provider. Do not build custom billing unless you have to. Customize the admin early. Write permission tests. Log important events. Ship the smallest paid workflow that a real customer can use safely.

[53:00]Gabriela: Let us close with the main lesson. What is SaaS, really?

[53:07]Leo Grant: SaaS is workflow software with money attached. The workflow has to work. The money has to be correct. The permissions have to be safe. The customer has to understand what is happening. Django is a strong foundation because it helps you model those responsibilities in one coherent system.

[54:05]Gabriela: So the point is not to make Django look trendy.

[54:09]Leo Grant: Right. The point is to build a product that survives real customers. Trends do not handle failed payments. Trends do not transfer ownership when a founder leaves. Trends do not answer support tickets. Your system does.

[54:35]Gabriela: Leo Grant, thanks for joining us.

[54:39]Leo Grant: Thanks for having me.

[54:44]Gabriela: For listeners, the takeaway is simple: do not build SaaS as a landing page with a login button. Build the tenant model, the permission model, the billing lifecycle, the admin workflow, and the receipts users need to trust you. That is Builder Notes. Thanks for listening.

[55:00]Gabriela: End.

More Django Episodes