Intégrations externes
Email, WhatsApp, transcription audio, notifications push
Reciprok est multi-canal. Cette page liste les intégrations externes nécessaires et les choix techniques pour chacune.
Vue d'ensemble
| Canal | Use cases | Provider retenu |
|---|---|---|
| Email sortant | Catalogue, factures, requalif, dispo | Resend + react-email (templates Dusty Bloom) |
| Email entrant | Creation de demande, reponses | Webhook Resend Inbound |
| Paiement | Checkout commissions, auto-mark paid | Stripe (Checkout Sessions + webhook) |
| Jumelage emails, vocal | API Meta directe (Cloud API) | |
| Cartographie | Carte membres 3D, catalogue public, hub demande | Mapbox GL JS (style Standard) |
| Audio (transcription) | Voice messages, audios membres | Whisper API (OpenAI) |
| Audio (generation TTS) | Lecture des messages AI | OpenAI TTS (V2) |
| Push notifications | Alertes desktop/PWA | Web Push API natif (VAPID) |
| Storage | Photos, audios, fichiers | Cloudflare R2 (S3-compatible) / RustFS (dev) |
| Logs HTTP | Access logs, acces, erreurs | Logixlysia → Loki (voir operations/monitoring) |
Email sortant
Provider
Resend (cf. ADR-05).
Justification résumée :
- API REST minimale, SDK TypeScript first-class
- Webhook inbound (
reciprok.com→ POST JSON) - Tracking ouvert/cliqué natif
- Compatible
react-email(templates en JSX) - Bonne délivrabilité (SPF/DKIM auto-configurés)
- Plan free 3 000 mails/mois suffit largement pour le démarrage
Architecture
Tracking
Pour chaque email envoyé, on stocke un email_message avec :
status:pending→sent→delivered→opened→clicked→bounced/failedproviderId: ID renvoyé par Resend pour matcher avec les webhooksopenedAt,clickedAt,bouncedAt
Templates react-email
8 templates dans apps/server/src/emails/, tous basees sur base-layout.tsx (style Dusty Bloom dark : fond #1e1c22, card arrondie #2a2730, accents #c8a87e).
| Template | Flow | Contenu |
|---|---|---|
base-layout.tsx | Commun | Header RECIPROK, card dark arrondie, footer |
commission-invoice.tsx | Facturer | Montant, taux, details membre + bouton Stripe |
catalog-sent.tsx | Envoyer catalogue | Liste membres recommandes/compatibles + CTA |
generic-message.tsx | Reply inbox, IA | Auto-wrap tout texte plain en template |
requalification.tsx | Requalifier | Champs manquants, message perso + CTA formulaire |
availability-request.tsx | Demande dispo | Dates cards, participants + CTA reponse |
winner-selected.tsx | Gagnant | Banniere succes verte, details evenement |
not-selected.tsx | Non retenu | Remerciement, encouragement, "on continue" |
member-onboarding.tsx | Onboarding | Bienvenue, 3 etapes setup + lien dashboard |
Auto-wrap : tout email plain (reply inbox, message IA) passe par communicationsService.sendEmail() qui detecte si le HTML est un document complet ou un fragment. Les fragments sont automatiquement wrapes dans GenericMessageEmail.
Preview : GET /api/email-preview/:template rend chaque template avec des donnees de demo. Liste sur GET /api/email-preview.
Dev : RESEND_DEV_TO redirige tous les emails vers l'owner du compte Resend (restriction plan free onboarding@resend.dev).
Threading
Tous les emails liés à une demande sont groupés dans un email_thread. Le thread est créé à la première communication. Chaque mail a un In-Reply-To et References pour que les clients mail (Gmail, Outlook) groupent correctement les réponses.
Email entrant
Stratégie
Utiliser le webhook entrant du provider (Resend permet de configurer un domaine pour recevoir des emails et POST le contenu en webhook JSON).
Endpoint : POST /api/webhooks/email/inbound
Le payload contient :
from,to,subject,text,html- Headers complets (pour le threading)
- Pièces jointes
Routing
L'email entrant peut être :
-
Une nouvelle demande (envoyé à
demandes@reciprok.com)- L'IA parse le contenu, extrait les champs, crée une
Requesten statutqualifying(besoin de validation humaine) - Crée ou retrouve l'
Organizerpar email - Log un
request_createdevent
- L'IA parse le contenu, extrait les champs, crée une
-
Une réponse à un thread existant
- Identifié par le
In-Reply-Toou par un token dans le subject - Ajouté comme
email_messagedans le thread - Log un
email_receivedevent - Si le thread est une requalification, l'IA tente d'extraire les nouveaux champs
- Identifié par le
-
Inconnu
- Envoyé dans une "inbox" non triée que l'utilisateur peut traiter manuellement
Provider
API Meta directe (WhatsApp Cloud API).
On part directement sur Meta plutôt que Twilio. Raisons :
- Twilio facture une marge fixe par message au-dessus du tarif Meta (~+25%) sans valeur ajoutée pour notre cas
- L'API Meta Cloud est désormais aussi simple à intégrer que Twilio (REST + webhooks)
- Pas de SDK obligatoire, on consomme l'API REST directement
- Le compte WhatsApp Business est déjà demandé pour Reciprok côté commercial, autant ne pas dupliquer le setup
- Médias (audio, image) gérés nativement par l'API Meta
Setup
- Compte Meta Business + numéro WhatsApp Business vérifié
- App Meta avec produit "WhatsApp" ajouté → token permanent (pas le token sandbox)
- Webhook configuré sur
https://app.reciprok.com/webhooks/whatsapp(hors du préfixe/api) - Env :
WHATSAPP_PHONE_NUMBER_ID,WHATSAPP_ACCESS_TOKEN,WHATSAPP_VERIFY_TOKEN,WHATSAPP_API_VERSION(defaultv21.0)
Use cases
- Jumelage email : quand un email part, option de jumeler avec un message WhatsApp (texte ou vocal)
- Notifications membres : quand un membre est recommandé, notification WhatsApp (en option)
- Réponses entrantes : un membre répond par WhatsApp à une demande de dispo → webhook → mise à jour de la demande
Messages vocaux WhatsApp
L'audio reçu via WhatsApp est téléchargé via l'API Meta (GET /{media_id}), transcrit avec Whisper, et traité comme un texte entrant.
Logs HTTP, Logixlysia
Plugin Elysia natif pour les access logs structurés (JSON). Branché dès l'instanciation de l'app, il loggue chaque requête avec level, time, method, pathname, status, duration, traceId. Les logs sortent sur stdout et sont consommés par Promtail → Loki → Grafana.
import logixlysia from 'logixlysia';
new Elysia()
.use(logixlysia({
config: {
ip: true,
customLogFormat: '{level} {method} {pathname} {status} {duration}',
},
}))Voir operations/monitoring pour la chaîne complète d'observabilité.
Cartographie (Mapbox GL)
Provider
Mapbox GL JS avec le style Standard (3D buildings, terrain, POIs natifs).
Use cases
- Page /map : carte interactive 3D de tous les membres, avec clustering GeoJSON GPU-rendered pour performance (3K+ membres)
- Vue publique catalogue : carte des membres visibles du catalogue envoyé a l'organisateur
- Page hub /requests/[id] : mini-carte des membres candidats dans le contexte de la demande
Architecture technique
mapbox-glcharge directement en client (dynamic importnext/dynamicavecssr: false)- Source GeoJSON + layers
circle+symbolpour les clusters (pas de DOM markers = performances GPU) - Style Standard affiche les POIs natifs (restaurants, hotels), pertinents pour le metier
- Env :
NEXT_PUBLIC_MAPBOX_TOKENdanspackages/env/src/web.ts - Pas de geocoding server-side, les coordonnees
lat/lngsont deja sur le membre
Composants
| Composant | Path | Usage |
|---|---|---|
MembersMap | components/map/members-map.tsx | Carte principale /map avec clusters |
MapLoader | components/map/map-loader.tsx | Dynamic import wrapper |
PublicCatalogMap | components/map/public-catalog-map.tsx | Carte vue publique catalogue |
Paiement (Stripe)
Provider
Stripe, Checkout Sessions pour le paiement des commissions.
Architecture
Flow facturation
- L'équipe clique "Facturer" sur une commission
- Le backend genere un Stripe Checkout Session avec
commissionIden metadata - Un email Dusty Bloom (
commission-invoice.tsx) est envoye au membre avec le bouton de paiement - Le membre clique, paie sur Stripe
- Stripe envoie
checkout.session.completedsurPOST /webhooks/stripe - Le webhook extrait
metadata.commissionIdet appellemarkPaid()automatiquement
Templates email
Voir la section Email sortant > Templates react-email pour la liste complete des 8 templates Dusty Bloom.
Env
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # from Stripe CLI or Dashboard
# Dev: stripe listen --forward-to http://localhost:3000/webhooks/stripe
RESEND_DEV_TO=your@email.com # override recipient in dev (Resend free plan restriction)Transcription audio (Whisper)
Provider
OpenAI Whisper API ou Deepgram.
- OpenAI Whisper : excellent en français, simple à utiliser, payé à la minute
- Deepgram : un peu plus rapide, support du streaming, prix similaire
Recommandation : OpenAI Whisper pour démarrer.
Use cases
- Audio de présentation d'un membre (long, asynchrone)
- Réponse vocale d'un organisateur à une requalification (court, asynchrone)
- Message vocal de l'utilisateur dans le chat IA (court, semi-temps réel)
Architecture
Pour les audios courts (chat IA), on peut transcrire synchrone (latence ~1-3s acceptable). Pour les longs, async avec notification quand prêt.
Notifications push
Provider
Web Push API native (pas besoin de Firebase ou autre).
- Service worker côté frontend
- VAPID keys côté backend
- Subscription stockée par utilisateur
- Push envoyé via la lib
web-push
Use cases
- Nouvelle demande créée
- Réponse à une requalification
- Réponse de dispo d'un membre
- Événement passé (rappel pour clôture)
- Alerte d'inactivité (48h sans action)
Architecture
Subscription :
Push d'une notification :
Storage
Provider
S3-compatible : Cloudflare R2 (gratuit jusqu'à 10 Go), Backblaze B2 (très bon prix), ou AWS S3 standard.
Recommandation : Cloudflare R2 pour démarrer (pas de frais de sortie, gratuit en dev).
Buckets
| Bucket | Contenu | Public ? |
|---|---|---|
member-photos | Photos des membres | Public (CDN) |
member-audios | Audios de présentation | Privé (URL signée) |
transcripts | Transcriptions JSON | Privé |
voice-messages | Audios de chat IA | Privé |
email-attachments | PJ des emails | Privé |
Génération d'URL signées
Pour les contenus privés, on génère des URL signées valides 1h pour l'affichage côté frontend. L'app n'expose jamais les URLs S3 directes.
Cron jobs et workers
Plusieurs tâches doivent tourner en background :
| Tâche | Fréquence |
|---|---|
| Re-embed les membres modifiés | Toutes les 5 min |
| Détecter les demandes inactives (48h) | Toutes les heures |
| Détecter les événements passés non clôturés | Toutes les heures |
| Cleanup des tokens magic expirés | Tous les jours |
| Envoi des emails de relance programmés | Toutes les 15 min |
Implémentation
Deux options :
- Bun + setInterval dans le serveur, simple mais lié au cycle de vie du serveur
- Worker dédié (process séparé) qui consomme une queue
Recommandation : commencer avec un worker Bun simple qui tourne en parallèle du serveur (process séparé via Dokploy), avec une queue Postgres LISTEN/NOTIFY pour les jobs ad hoc.
Variables d'environnement
# Database
DATABASE_URL=
# Better Auth
BETTER_AUTH_SECRET=
BETTER_AUTH_URL=
CORS_ORIGIN=
# Anthropic
ANTHROPIC_API_KEY=
# OpenAI (Whisper + embeddings)
OPENAI_API_KEY=
# Email (Resend example)
RESEND_API_KEY=
EMAIL_FROM=demandes@reciprok.com
EMAIL_INBOUND_DOMAIN=in.reciprok.com
EMAIL_WEBHOOK_SECRET=
# WhatsApp (Meta Cloud API)
META_WHATSAPP_TOKEN=
META_WHATSAPP_PHONE_ID=
META_WHATSAPP_VERIFY_TOKEN=
# Storage (R2 example)
S3_ENDPOINT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET_PHOTOS=
S3_BUCKET_AUDIOS=
# Push notifications
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
VAPID_SUBJECT=mailto:hello@reciprok.comÀ gérer via packages/env/ avec @t3-oss/env-core (déjà en place).