Retour au blog
Fonctionnalités

Multi-Tenant B2B : Junyr vs Alternatives

15 décembre 20259 min

Multi-tenant production-grade : isolation par schéma PostgreSQL, domaines vérifiés, white-label, admins multi-niveaux. Agences B2B.

Multi-Tenant B2B : Junyr vs Alternatives

En 2026, le multi-tenant est la norme pour les SaaS B2B. Mais tous les multi-tenants ne se valent pas. Junyr offre une isolation production-grade que les concurrents n'ont pas.


🏢 Le Multi-Tenant : Pourquoi C'est Critique

Définition

Multi-tenant = 1 instance logicielle partagée par plusieurs "tenants" (entreprises clientes), avec isolation des données.

Use Case B2B

Scénario : Agence propose Junyr en white-label à ses clients

Client A (Acme Corp)     : 50 users, 10 Junyrs
Client B (Tech Inc)      : 20 users, 5 Junyrs
Client C (Retail Ltd)    : 100 users, 15 Junyrs

Requis :
✅ Isolation totale des données (Client A ne voit PAS Client B)
✅ Domaines custom (sales@acme.com, hr@techinc.com)
✅ Branding personnalisé (logo, couleurs)
✅ Admins par client (pas de super-admin unique)
✅ Facturation séparée (Stripe par client)

📊 Comparaison Multi-Tenant

AspectJunyrn8nMakeZapier
ArchitectureCompany-based (natif)Workspaces (basique)Organizations (limité)Workspaces (limité)
Isolation données✅ Row-level security (PostgreSQL)🟡 Workspace ID (app-level)🟡 Org ID (app-level)🟡 Workspace ID (app-level)
Domaines email✅ Multi-domaines vérifiés❌ Aucun❌ Aucun❌ Aucun
Auto-registration✅ Via domaines vérifiés❌ Invites manuelles❌ Invites manuelles❌ Invites manuelles
White-label✅ Logo, couleurs, fonts❌ Marque n8n visible❌ Marque Make visible❌ Marque Zapier visible
Admins multi-niveaux✅ Super-admin + company-admin🟡 Owner + members🟡 Owner + members🟡 Owner + members
Facturation séparée✅ Stripe par company🟡 Manuelle🟡 Manuelle🟡 Manuelle

🏗️ Architecture Junyr : Production-Grade

Database Schema Multi-Tenant

-- Companies (tenants)
CREATE TABLE business_companies (
    id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(100) UNIQUE,
    siren VARCHAR(9),
    siret VARCHAR(14),
    created_at TIMESTAMP WITH TIME ZONE
);

-- Users appartiennent à une Company
CREATE TABLE users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    company_id UUID REFERENCES business_companies(id),
    role VARCHAR(50),  -- 'user', 'company_admin', 'super_admin'
    created_at TIMESTAMP WITH TIME ZONE
);

-- Domaines email vérifiés
CREATE TABLE company_domains (
    id UUID PRIMARY KEY,
    company_id UUID REFERENCES business_companies(id),
    domain VARCHAR(255) NOT NULL,
    is_verified BOOLEAN DEFAULT FALSE,
    verified_at TIMESTAMP WITH TIME ZONE,
    verification_method VARCHAR(20)  -- 'dns', 'email', 'mx'
);

-- Admins par Company (junction table)
CREATE TABLE admin_companies (
    user_id UUID REFERENCES users(id),
    company_id UUID REFERENCES business_companies(id),
    PRIMARY KEY (user_id, company_id)
);

-- Agents (Junyrs) appartiennent à un owner ET company
CREATE TABLE agents (
    id UUID PRIMARY KEY,
    user_id UUID REFERENCES users(id),  -- Owner
    company_id UUID REFERENCES business_companies(id),  -- Isolation
    name VARCHAR(255),
    email VARCHAR(255),
    created_at TIMESTAMP WITH TIME ZONE
);

-- Row-Level Security (RLS)
CREATE POLICY company_isolation ON agents
    USING (company_id = current_setting('app.current_company')::UUID);

Isolation Garanties

TypeMéthodeDétails
DonnéesRow-Level Security (PostgreSQL)Politique RLS sur TOUTES les tables (users, agents, projects, messages)
APICompany ID validationMiddleware vérifie current_user.company_id sur chaque requête
EmailDomain verificationSeuls domaines vérifiés peuvent auto-register
FacturationStripe par companysubscription.company_id sépare les factures
UICompany contextSidebar affiche user.company.name (ex: "Acme Corp")

🔐 Vérification Domaines : Sécurité Renforcée

Junyr : 2-Level Verification

┌─────────────────────────────────────────────────────────────────┐
│  NIVEAU 1 : Ownership (Prouver que vous possédez le domaine)   │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Méthode A : DNS TXT Record                                     │
│  └─ Ajouter TXT : junyr-verify=abc123xyz                        │
│     → Vérifie propriété (enable auto-registration)             │
│                                                                 │
│  Méthode B : Email Verification                                 │
│  └─ Code 6 chiffres à admin@domain.com                          │
│     → Vérifie accès administratif                              │
│                                                                 │
│  ✅ Résultat : is_verified = TRUE                               │
│  → Users @domain.com peuvent auto-join company                  │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│  NIVEAU 2 : Email Reception (Recevoir emails via Junyr)        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Méthode : MX Record Configuration                              │
│  └─ MX 10 mail.junyr.app                                        │
│  └─ TXT "v=spf1 mx include:junyr.app -all"                      │
│                                                                 │
│  ✅ Résultat : email_reception_ready = TRUE                     │
│  → Junyrs peuvent recevoir emails à sales@domain.com            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

n8n/Make/Zapier : Aucune Vérification

❌ Pas de système de domaines
❌ Pas de vérification ownership
❌ Pas d'auto-registration sécurisée
❌ Invites manuelles uniquement (risque email typo)

Problème Sécurité :

Scénario n8n :
1. Admin invite user@acme.com (typo : user@acne.com)
2. Email va à mauvaise personne
3. Mauvaise personne accepte l'invite
4. Accès workspace avec données sensibles

VS Junyr :
1. Domain acme.com vérifié (DNS TXT + code email)
2. user@acme.com auto-join après validation email
3. user@acne.com BLOQUÉ (domaine non vérifié)
4. Zéro risque d'accès non autorisé

👥 Auto-Registration : UX Supérieure

Junyr : Auto-Join Sécurisé

Workflow auto-registration :

1. User visite https://junyr.app/signup
2. Entre email : john@acme.com
3. Backend vérifie :
   ✅ Domain "acme.com" existe dans company_domains
   ✅ is_verified = TRUE
4. User créé avec company_id = Acme Corp
5. Email confirmation envoyé
6. User clique lien → Auto-join Acme Corp

Résultat :
✅ John voit immédiatement les Junyrs de l'équipe
✅ John peut collaborer avec collègues
✅ Aucune action admin requise

n8n/Make/Zapier : Invites Manuelles

Workflow invites :

1. Admin ouvre "Team Settings"
2. Clique "Invite Member"
3. Entre manuellement email : john@acme.com
4. System envoie email invite
5. John reçoit email, clique lien
6. John accepte invite
7. Admin doit assigner rôle manuellement

Problèmes :
❌ Admin doit inviter CHAQUE user (scaling difficile)
❌ Risque typo email (user@acne.com vs user@acme.com)
❌ Pas d'onboarding automatisé
❌ John ne voit rien avant invite

🎨 White-Label : Branding Complet

Junyr : Personnalisation Totale

Settings > Branding (company-level) :

✅ Logo upload (PNG/JPG/SVG, max 2MB)
✅ Primary color (#0066cc)
✅ Secondary color (#f5f5f5)
✅ Font family (Inter, Roboto, Poppins, etc.)

Appliqué à :
- PDF exports (header logo + couleurs)
- Excel exports (styles personnalisés)
- Email signatures (logo + couleurs)
- UI theme (primary color dans boutons, badges)

Use case : Agence white-label
→ Client A voit logo Acme + bleu
→ Client B voit logo Tech Inc + vert
→ Jamais la marque "Junyr" visible

n8n/Make/Zapier : Marque Visible

❌ Logo n8n/Make/Zapier dans UI (non modifiable)
❌ Branding dans emails notifications
❌ Footer "Powered by n8n/Make/Zapier"

→ Impossible de revendre en white-label

🔑 Admins Multi-Niveaux

Junyr : 3 Rôles Hiérarchiques

┌─────────────────────────────────────────────────────────────────┐
│  HIERARCHY ROLES                                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  SUPER_ADMIN (Junyr Team)                                       │
│  ├─ Gère TOUTES les companies                                   │
│  ├─ Accès pricing, AI models, platform config                   │
│  └─ Peut devenir admin de n'importe quelle company             │
│      ↓                                                          │
│  COMPANY_ADMIN (Client A, Client B, etc.)                       │
│  ├─ Gère SA company uniquement                                  │
│  ├─ Invite users, manage Junyrs, configure domains             │
│  └─ Voit TOUS les Junyrs de sa company (pas juste les siens)   │
│      ↓                                                          │
│  USER (Membres équipe)                                          │
│  ├─ Voit les Junyrs de sa company                              │
│  ├─ Peut interagir avec ses propres Junyrs                     │
│  └─ Pas de gestion admin                                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

n8n/Make/Zapier : 2 Rôles Basiques

OWNER (1 seul par workspace)
├─ Tous les droits
└─ Peut inviter members

MEMBER
├─ Peut voir workflows
└─ Peut exécuter (selon permissions)

❌ Pas de multi-level admin
❌ Pas de délégation granulaire
❌ Scaling difficile (1 owner bottleneck)

💳 Facturation Séparée : Stripe Multi-Tenant

Junyr : Stripe Customer Par Company

# Backend : stripe_service.py

async def create_plan_subscription_checkout(
    company_id: str,
    plan_id: str,
    user_email: str
):
    # 1. Récupérer company
    company = await db.fetchrow(
        "SELECT * FROM business_companies WHERE id = $1",
        company_id
    )

    # 2. Créer Stripe Customer (1 par company)
    customer = stripe.Customer.create(
        email=user_email,
        metadata={
            "company_id": company_id,
            "company_name": company["name"]
        }
    )

    # 3. Créer checkout session
    session = stripe.checkout.Session.create(
        customer=customer.id,
        line_items=[{
            "price": PLAN_PRICES[plan_id],
            "quantity": 1
        }],
        mode="subscription",
        metadata={"company_id": company_id}  # Tracking
    )

    # 4. Stocker subscription
    await db.execute(
        "INSERT INTO subscriptions (company_id, stripe_subscription_id, ...)"
    )

Résultat :

  • ✅ 1 facture Stripe par company (séparation claire)
  • ✅ Metadata company_id pour tracking
  • ✅ Webhooks Stripe → update status par company

n8n/Make/Zapier : Facturation Manuelle

❌ Pas de Stripe multi-tenant natif
❌ Admin doit créer subscriptions manuellement
❌ Facturation agrégée (impossible de séparer par client)

→ Complexe pour revente white-label

📊 Cas d'Usage Réel : Agence B2B

Scénario : Agence Propose Junyr à 10 Clients

Avec Junyr (Multi-Tenant Natif)

Setup (1 fois) :
1. Agence crée 10 companies dans Junyr Admin
   - Acme Corp (client A)
   - Tech Inc (client B)
   - ...
   - Retail Ltd (client J)

2. Vérifier domaines clients :
   - acme.com (DNS TXT + MX)
   - techinc.com (DNS TXT + MX)
   - ...

3. Configurer white-label branding par client

4. Créer Stripe subscriptions par company

Utilisation (automatique) :
✅ Users clients auto-join via email @domain.com
✅ Isolation totale (Acme ne voit PAS Tech Inc)
✅ Facturation séparée (10 factures Stripe)
✅ Branding personnalisé (10 logos différents)

Maintenance : 0 heures/mois (automatique)

Avec n8n/Make/Zapier (Pas Multi-Tenant)

Setup (laborieux) :
1. Créer 10 workspaces séparées
   - Workspace "Acme Corp"
   - Workspace "Tech Inc"
   - ...

2. Inviter TOUS les users manuellement (1 par 1)
   - john@acme.com → invite manuelle
   - jane@acme.com → invite manuelle
   - ...
   → 100+ invites si 10 users/client

3. ❌ Impossible de white-label (marque visible)

4. ❌ Facturation manuelle par workspace

Utilisation (manuelle) :
❌ Admin doit inviter chaque nouveau user
❌ Pas d'auto-registration sécurisée
❌ Marque n8n/Make/Zapier visible (pas white-label)
❌ Facturation complexe (10 workspaces à gérer)

Maintenance : 10-20 heures/mois (invites, support)

Gain Junyr : 10-20 heures/mois + white-label possible


🎯 Matrice de Choix

Use CaseRecommandationPourquoi
Agence white-labelJunyrMulti-tenant natif + branding
Startup (1 équipe)n8n/Make/ZapierWorkspaces suffisants
SaaS B2B multi-clientsJunyrIsolation + auto-registration
FreelanceJunyr Personal1 company = suffisant
Grande entreprise multi-BUJunyr EnterpriseMulti-admin + isolation

💡 Conclusion

Le multi-tenant n'est pas juste une "feature bonus". Pour les agences et SaaS B2B, c'est une architecture critique.

n8n/Make/Zapier : Workspaces basiques (1 équipe par workspace) Junyr : Multi-tenant production-grade (isolation, domaines, branding, facturation)

Les bénéfices Junyr :

  • ✅ Isolation row-level (sécurité PostgreSQL)
  • ✅ Domaines vérifiés (auto-registration sécurisée)
  • ✅ White-label complet (logo, couleurs, fonts)
  • ✅ Facturation Stripe séparée par company
  • ✅ Admins multi-niveaux (super-admin, company-admin, user)
  • ✅ Gain 10-20h/mois maintenance (agences)

Si vous revendez à des clients : Junyr est structurellement supérieur.


📈 Aller Plus Loin

#multi-tenant#b2b#white-label#domaines#agences
JT

Junyr Team

Plateforme IA

L'equipe Junyr conçoit des outils d'IA qui permettent aux TPE/PME européennes de recruter, former et piloter des agents IA autonomes pour leurs tâches quotidiennes.