Mistral AI dans un SaaS B2B : 5 use-cases concrets en TypeScript
Intégrer une IA souveraine française dans votre SaaS — extraction de données, résumés clients, classification d'emails, génération de contenu et search sémantique.
Pourquoi Mistral plutôt qu'OpenAI ?
Trois raisons concrètes pour un SaaS B2B français :
- Souveraineté des données — vos données clients ne sortent pas de l'UE. Argument RGPD béton face à un acheteur grand compte.
- Prix —
mistral-small-latestcoûte ~10x moins cher que GPT-4o pour des tâches d'extraction structurée. - Latence en EU — les serveurs Mistral sont à Paris, pas en Virginie. Vos webhooks répondent en 200ms au lieu de 600ms.
Setup minimal :
// src/lib/ai/mistral.ts
import { Mistral } from "@mistralai/mistralai";
import { env } from "~/env";
export const mistral = new Mistral({ apiKey: env.MISTRAL_API_KEY });Use case 1 — Extraction de données depuis un PDF
Le client envoie une facture fournisseur en PDF. Vous voulez extraire automatiquement le montant, la date, le numéro.
import { z } from "zod";
const InvoiceSchema = z.object({
invoiceNumber: z.string(),
date: z.string(),
totalHT: z.number(),
totalTTC: z.number(),
vatRate: z.number(),
supplierName: z.string(),
});
export async function extractInvoiceFromText(text: string) {
const response = await mistral.chat.complete({
model: "mistral-small-latest",
responseFormat: { type: "json_object" },
messages: [
{
role: "system",
content:
"Tu extrais les données structurées de factures françaises. Réponds uniquement en JSON valide.",
},
{
role: "user",
content: `Extrait ces champs de la facture : invoiceNumber, date (YYYY-MM-DD), totalHT, totalTTC, vatRate, supplierName.
Facture :
${text}`,
},
],
});
const raw = response.choices[0]?.message.content;
if (typeof raw !== "string") throw new Error("Empty response");
return InvoiceSchema.parse(JSON.parse(raw));
}Use case 2 — Résumé d'historique client pour le CRM
Quand un commercial ouvre une fiche client, il veut comprendre en 5 secondes ce qui s'est passé : derniers échanges, signaux d'achat, points de friction.
// src/server/api/routers/crm.ts
import { mistral } from "~/lib/ai/mistral";
summarizeClientHistory: requirePermission("crm:read")
.input(z.object({ clientId: z.string() }))
.query(async ({ ctx, input }) => {
const interactions = await ctx.orgDb.clientInteraction.findMany({
where: { clientId: input.clientId },
orderBy: { createdAt: "desc" },
take: 20,
});
if (interactions.length === 0) return null;
const transcript = interactions
.map((i) => `[${i.type}] ${i.createdAt.toISOString()} : ${i.content}`)
.join("\n");
const response = await mistral.chat.complete({
model: "mistral-small-latest",
maxTokens: 200,
messages: [
{
role: "system",
content:
"Tu es un assistant CRM. Résume en 3 puces : statut actuel, dernier signal d'achat, prochaine action recommandée.",
},
{ role: "user", content: transcript },
],
});
return response.choices[0]?.message.content ?? null;
}),Use case 3 — Classification d'emails entrants
Vous recevez les emails clients dans une boîte support. Vous voulez les router automatiquement vers le bon agent.
const Category = z.enum([
"BUG_REPORT",
"FEATURE_REQUEST",
"BILLING",
"ONBOARDING",
"CHURN_RISK",
"OTHER",
]);
export async function classifyEmail(subject: string, body: string) {
const response = await mistral.chat.complete({
model: "mistral-small-latest",
responseFormat: { type: "json_object" },
maxTokens: 50,
messages: [
{
role: "system",
content: `Tu classifies des emails de support. Réponds en JSON : {"category": "...", "urgent": boolean}.
Catégories valides : BUG_REPORT, FEATURE_REQUEST, BILLING, ONBOARDING, CHURN_RISK, OTHER.`,
},
{ role: "user", content: `Sujet : ${subject}\n\n${body}` },
],
});
const result = z
.object({ category: Category, urgent: z.boolean() })
.parse(JSON.parse(response.choices[0]?.message.content ?? "{}"));
return result;
}Performance typique : 92% de précision après 200 emails labellisés à la main pour few-shot prompting.
Use case 4 — Génération de contenu (descriptions produits)
Un utilisateur ajoute un produit dans son catalogue. Il rentre le nom et 3 caractéristiques. Vous générez la description marketing.
generateProductDescription: requirePermission("catalog:write")
.input(
z.object({
productName: z.string(),
keyFeatures: z.array(z.string()).min(1).max(5),
tone: z.enum(["pro", "casual", "premium"]).default("pro"),
}),
)
.mutation(async ({ ctx, input }) => {
// Freemium guard avant l'appel
const guard = await checkFreemiumLimit(ctx, "aiGenerations");
if (!guard.allowed) {
throw new TRPCError({
code: "FORBIDDEN",
message: `Quota IA atteint (${guard.used}/${guard.limit})`,
});
}
const response = await mistral.chat.complete({
model: "mistral-small-latest",
maxTokens: 250,
messages: [
{
role: "system",
content: `Tu rédiges des descriptions produit en français, ton ${input.tone}. 80 mots max.`,
},
{
role: "user",
content: `Produit : ${input.productName}\nCaractéristiques :\n- ${input.keyFeatures.join("\n- ")}`,
},
],
});
// Incrémenter le compteur APRÈS le succès
await ctx.db.subscription.update({
where: { organizationId: ctx.session.user.organizationId },
data: { aiGenerationsUsed: { increment: 1 } },
});
return response.choices[0]?.message.content ?? "";
}),Use case 5 — Search sémantique avec embeddings
L'utilisateur tape "facture impayée Dupont" et vous voulez retrouver des résultats même si le mot exact n'est pas dans la base.
// 1. À la création d'une ressource, calculer son embedding
export async function indexDocument(id: string, text: string) {
const response = await mistral.embeddings.create({
model: "mistral-embed",
inputs: [text],
});
const embedding = response.data[0]?.embedding;
if (!embedding) throw new Error("Embedding failed");
// Stockage avec pgvector
await db.$executeRaw`
UPDATE "Document"
SET embedding = ${embedding}::vector
WHERE id = ${id}
`;
}
// 2. Au moment de la recherche
export async function semanticSearch(query: string, orgId: string) {
const queryEmbedding = (
await mistral.embeddings.create({
model: "mistral-embed",
inputs: [query],
})
).data[0]?.embedding;
return db.$queryRaw`
SELECT id, title, 1 - (embedding <=> ${queryEmbedding}::vector) AS similarity
FROM "Document"
WHERE "organizationId" = ${orgId}
ORDER BY embedding <=> ${queryEmbedding}::vector
LIMIT 10
`;
}Coût : mistral-embed est à 0.10€ / 1M tokens. Indexer 10 000 documents de 500 mots = ~50 centimes. La recherche elle-même coûte des millièmes de centime.
Le pattern de coût global
| Use case | Coût/appel | Fréquence typique | Coût mensuel (1000 users) |
|---|---|---|---|
| Extraction PDF | ~0.005€ | 5/user/mois | 25€ |
| Résumé CRM | ~0.002€ | 30/user/mois (cached) | 60€ |
| Classification email | ~0.0003€ | 50/user/mois | 15€ |
| Génération produit | ~0.003€ | 10/user/mois | 30€ |
| Embedding (index + search) | ~0.0001€ | 200/user/mois | 20€ |
| Total | ~150€/mois |
Vous facturez vos plans 30-150€/mois. La marge sur l'IA est confortable.
Erreurs à éviter
- Appeler Mistral depuis un Server Component sans cache — un refresh = un appel API. Toujours wrapper avec
unstable_cacheou Redis. - Pas de retry sur erreur réseau — Mistral a un SLA de 99.9% mais des spikes. Implémentez 3 retries avec backoff exponentiel.
- Streamer dans une Edge Function de plus de 25s — Vercel coupe à 30s. Pour les longs streams, utilisez un endpoint Node.js classique.
- Oublier le freemium guard avant l'appel — sans ça, un utilisateur du plan gratuit peut consommer 1000€ d'IA en 1 nuit.
Conclusion
Mistral n'est pas "OpenAI mais français". C'est une stack adaptée aux SaaS européens : RGPD-friendly, économique, et avec des modèles assez bons pour 90% des cas d'usage B2B. Pour les 10% restants (raisonnement complexe, code generation), gardez un fallback vers Claude ou GPT-4o — mais commencez par Mistral.
Dans HeartCo, l'intégration Mistral est pré-câblée : freemium guard, retry, cache Redis, et exemples dans src/lib/ai/. Vous ajoutez votre use case en 30 lignes.
Articles connexes
Lancer un SaaS sans coder en 2026 — la stack hybride no-code + code
Comment passer de l'idée au SaaS en production sans écrire une ligne de Stripe ou de NextAuth — avec un assistant no-code qui génère le code pour vous.
LireRGPD pour SaaS B2B français — checklist complète sans mythes
Ce que vous devez vraiment faire pour être conforme RGPD en tant que SaaS B2B français — et ce que les agences de conseil vous vendent inutilement.
LireShipFast vs HeartCo : quel boilerplate SaaS choisir en 2026 ?
Comparaison directe et honnête entre ShipFast et HeartCo — stack technique, features, prix, et pour quel type de projet chaque solution convient vraiment.
Lire