Plongée technique : architecture et algorithmes de routage SMS par correspondance opérateur

Introduction : les coulisses du routage SMS par correspondance opérateur

La plupart des fournisseurs SMS décrivent le routage comme une boîte noire :

« Nous utiliserons la meilleure route en fonction de la qualité et du prix. »

Si vous êtes ingénieur ou architecte responsable de la disponibilité, ce n'est pas suffisant. Vous devez savoir :

  • Quel chemin un message a suivi.
  • Quel expéditeur a été utilisé.
  • Pourquoi le système a choisi cette combinaison.
  • Comment il se comportera en cas de pannes et de pics de trafic.

Le routage par correspondance opérateur est l'une des raisons principales pour lesquelles certaines passerelles atteignent durablement 99,4 % et plus de délivrabilité dans des verticales difficiles, tandis que d'autres stagnent entre 90 et 95 %. Dans cet article, nous allons explorer en détail l'architecture :

  • La couche d'intelligence (détection de l'opérateur et du type de ligne).
  • Le moteur de décision de routage.
  • La sélection de pools/grilles et la logique de rotation.
  • Les stratégies de repli.
  • L'observabilité et le débogage.

Ceci n'est pas du marketing fournisseur. C'est l'architecture concrète que nous avons vue fonctionner sur des millions de messages par jour.


Section 1 : ce que signifie réellement la « correspondance opérateur »

À un niveau général, la correspondance opérateur consiste à :

Pour chaque numéro de destination, choisir un expéditeur et une route qui correspondent le mieux à l'opérateur et au contexte de la destination.

Plutôt que de :

  • Tout envoyer via les routes génériques les moins chères.
  • Mélanger tous les opérateurs et tous les cas d'usage sur les mêmes expéditeurs.

La correspondance opérateur vise à :

  • Utiliser des expéditeurs validés Verizon pour les abonnés Verizon.
  • Utiliser des expéditeurs validés AT&T pour les abonnés AT&T.
  • Maintenir une réputation par opérateur isolée et prévisible.

Bénéfices observés en pratique :

  • Un gain de délivrabilité de 3 à 12 points sur certains opérateurs par rapport au routage générique.
  • Une variance réduite des performances dans le temps.
  • Une analyse des causes profondes plus claire en cas de problème (un opérateur, une grille).

Section 2 : architecture de haut niveau

Une passerelle SMS à correspondance opérateur comporte généralement les composants suivants :

  1. API d'entrée (ingress)

    • Reçoit les requêtes de messages (/messages).
    • Valide la charge utile, l'authentification et le schéma de base.
  2. Normalisation et enrichissement

    • Normalise les numéros de téléphone (E.164).
    • Enrichit avec :
      • Les informations sur l'opérateur.
      • Le pays/la région.
      • Le type de ligne (mobile, VoIP, fixe lorsque disponible).
      • Les signaux de risque.
  3. Moteur de décision de routage

    • À partir du contexte enrichi et des métadonnées applicatives :
      • Choisit un profil de route (par ex., OTP_US, Promo_EU).
      • Sélectionne un pool/grille.
      • Choisit un expéditeur au sein de cette grille.
    • Applique des règles par opérateur et par grille.
  4. Mise en file d'attente et envoi

    • Place les messages dans des files d'attente par route.
    • Applique :
      • Une limitation de débit.
      • Un contrôle des pics.
      • Des stratégies de réessai.
  5. Accusés de réception et retour d'information

    • Ingère les DLR (accusés de réception de livraison).
    • Met à jour :
      • La santé du pool/de la grille.
      • Les métriques de réputation des expéditeurs.
    • Alimente en retour les décisions de routage.
  6. Plan d'observabilité

    • Métriques, journaux, traces.
    • Interrogeable par :
      • Opérateur.
      • Pool/grille.
      • Expéditeur.
      • Campagne.

Section 3 : la couche d'intelligence opérateur

Avant de pouvoir faire correspondre les opérateurs, il faut les connaître.

Entrées

  • Numéro de téléphone au format E.164.
  • Optionnellement :
    • Le code pays issu du contexte applicatif.
    • Des métadonnées utilisateur connues (par ex., opérateur déjà résolu précédemment).

Sources

  • Fournisseurs de lookup HLR / opérateur.
  • API de renseignement sur les numéros de téléphone.
  • Caches internes (numéros récemment résolus).

Sorties

Pour une destination donnée :

  • carrier_id : par ex., verizon_us, att_us, tmobile_us, o2_uk, etc.
  • country_code : US, GB, DE, etc.
  • line_type : mobile, fixed, voip (lorsque disponible).
  • risk_flags : portabilité récente, plages suspectes, etc. (optionnel).

Stratégie de mise en cache

  • Préchauffer les caches sur :
    • Les destinations à fort trafic.
    • Les expéditeurs fréquents connus (par ex., utilisateurs intensifs d'OTP).
  • Respecter :
    • Les limites de débit du fournisseur de lookup.
    • Les contraintes de fraîcheur des données.

Exemple (pseudo-code) :

type CarrierInfo = {
  carrierId: string;
  country: string;
  lineType?: string;
  lastUpdated: number;
};

async function resolveCarrier(msisdn: string): Promise<CarrierInfo> {
  const cached = await carrierCache.get(msisdn);
  if (cached && Date.now() - cached.lastUpdated < CACHE_TTL_MS) {
    return cached;
  }

  const lookup = await externalLookup(msisdn);

  const info: CarrierInfo = {
    carrierId: lookup.carrierId,
    country: lookup.countryCode,
    lineType: lookup.lineType,
    lastUpdated: Date.now(),
  };

  carrierCache.set(msisdn, info);
  return info;
}

Section 4 : conception du moteur de décision de routage

Étant donné :

  • Le contexte de message enrichi (CarrierInfo, pays, métadonnées applicatives).
  • Le type de message (OTP, transactionnel, marketing).
  • La configuration du client/compte.

Le moteur de routage doit choisir :

  1. Le profil de route

    • Par ex., OTP_US, PROMO_US, ALERT_EU, etc.
    • Encapsule :
      • Les opérateurs/routes préférés.
      • Les plafonds de débit.
      • Les types d'expéditeurs autorisés.
  2. Le pool / la grille

    • Par ex., US_OTP_VERIZON_GRID_A, US_PROMO_ATT_GRID_B.
    • Chaque grille :
      • Représente un ensemble de cartes SIM/numéros.
      • Possède une capacité et des métriques de santé par opérateur.
  3. L'expéditeur au sein de la grille

    • Selon :
      • La stratégie de rotation.
      • La santé.
      • Les contraintes locales.

Flux de décision (simplifié)

function routeMessage(msg: Message, carrier: CarrierInfo): RouteDecision {
  const profile = selectProfile(msg, carrier);

  const candidateGrids = findEligibleGrids(profile, carrier);

  const grid = selectBestGrid(candidateGrids);

  const sender = pickSenderFromGrid(grid, msg);

  return { profileId: profile.id, gridId: grid.id, senderId: sender.id };
}

Où :

  • selectProfile utilise :

    • Le type de message (OTP vs promo).
    • Le pays/la région.
    • Le risque/la verticale (par ex., crypto/adulte).
  • findEligibleGrids filtre par :

    • Pays.
    • Compatibilité opérateur.
    • Seuils de santé.
  • selectBestGrid peut :

    • Privilégier les grilles avec :
      • Des taux d'erreur/de plaintes sains.
      • De la capacité disponible.
    • Éviter :
      • Les grilles approchant des seuils critiques.
  • pickSenderFromGrid :

    • Implémente une rotation :
      • Round-robin (tour à tour).
      • Pondérée.
      • Tenant compte de la santé (en évitant les mauvais expéditeurs).

Section 5 : pools/grilles et logique de rotation

Les grilles comme unité principale d'isolation

Une grille peut être définie par :

  • La région : US.
  • Le mix d'opérateurs : Verizon uniquement, AT&T uniquement, multi-opérateurs.
  • Le cas d'usage : OTP, PROMO, ALERT.
  • Le niveau de priorité.

Chaque grille suit :

  • Le total des envois.
  • La répartition livrés/échecs.
  • Les codes d'échec définitif (hard-fail).
  • Les taux de plaintes/désabonnements.

Stratégies de rotation

La plus simple :

  • Round-robin entre les expéditeurs actifs.

Une meilleure approche :

  • Rotation tenant compte de la santé :
    • Ignorer les expéditeurs avec :
      • Des taux d'erreur récents élevés.
      • Des ratios de plaintes élevés.
    • Favoriser :
      • Les expéditeurs plus récents et en bonne santé.

Exemple :

function pickSenderFromGrid(grid: GridState): Sender {
  const healthy = grid.senders.filter((s) => s.healthScore > MIN_HEALTH);
  const weighted = buildWeightedList(healthy, (s) => s.weight);
  return randomChoice(weighted);
}

Avec :

  • healthScore basé sur :
    • Le taux de livraison récent.
    • Le taux d'échec définitif.
    • Le taux de plaintes.
    • Le temps écoulé depuis la dernière vérification/montée en charge progressive (warmup).

Mise au repos et période de récupération

Mettre en place des règles telles que :

  • Retirer ou mettre au repos un expéditeur lorsque :
    • Le taux d'échec définitif dépasse 1 à 2 % sur les N derniers messages.
    • Les plaintes dépassent 0,3 à 0,5 % sur une période donnée.
    • Les codes d'erreur spécifiques à l'opérateur s'envolent.

Les expéditeurs retirés :

  • Sont sortis de la rotation active.
  • Peuvent être retestés plus tard avec un trafic faible et sans risque.

Section 6 : mécanismes de repli, réessais et modes de défaillance

Même avec un bon routage, des incidents surviennent :

  • Les opérateurs subissent des pannes.
  • Certaines routes se dégradent.
  • Une grille se trouve temporairement « grillée ».

Principes de repli

  1. Privilégier d'abord les repli au sein de la même famille

    • Passer de la Grille A à la Grille B au sein du même profil/pays.
    • Garder l'OTP sur les grilles OTP, les promos sur les grilles promo.
  2. Éviter les réessais instantanés et répétés sur le même chemin défaillant

    • Reculer de manière agressive :
      • Backoff exponentiel ou linéaire.
    • Marquer les routes/grilles défaillantes comme dégradées.
  3. Dégradation maîtrisée

    • Pour l'OTP :
      • Essayer un expéditeur alternatif au sein de la même famille d'opérateur.
      • Envisager un repli plus lent mais plus fiable.
    • Pour les promos :
      • Réduire le débit d'envoi.
      • Différer les envois si les opérateurs sont clairement instables.

Exemple de logique de réessai (simplifiée)

async function dispatchMessage(decision: RouteDecision, msg: Message) {
  try {
    const result = await sendToCarrier(decision, msg);

    updateMetrics(decision, result);
    return result;
  } catch (err) {
    markRouteAsDegraded(decision, err);

    const fallbackDecision = findFallback(decision, msg);
    if (!fallbackDecision) throw err;

    const fallbackResult = await sendToCarrier(fallbackDecision, msg);
    updateMetrics(fallbackDecision, fallbackResult);
    return fallbackResult;
  }
}

Section 7 : observabilité, journalisation et débogage

Le routage par correspondance opérateur ne vaut que ce que vaut son observabilité.

Vous devez pouvoir poser des questions comme :

  • « Montrez-moi tous les messages vers Verizon des dernières 24 heures, routés via la Grille A vs la Grille B. »
  • « Quels expéditeurs de la Grille C ont le taux d'échec définitif le plus élevé ? »
  • « Qu'est-ce qui a changé au moment où la délivrabilité a chuté ? »

Champs de journalisation minimaux

Pour chaque message :

  • message_id
  • timestamp
  • customer_id (ou identifiant de projet/application)
  • destination_msisdn (haché/pseudonymisé si nécessaire)
  • carrier_id
  • country_code
  • profile_id
  • grid_id
  • sender_id
  • route_id / identifiant en amont
  • status (en file d'attente, envoyé, livré, échoué, inconnu)
  • error_code (le cas échéant)
  • dlr_timestamp
  • latency_ms
  • campaign_id ou flow_id (le cas échéant)

Tableaux de bord

  • Cartes de chaleur opérateur × grille :
    • Taux de livraison.
    • Taux d'échec définitif.
  • Classements des expéditeurs :
    • Triés par santé et débit.
  • Détection d'anomalies :
    • Alertes lorsque :
      • Le taux de livraison de l'opérateur X, Grille Y, tombe sous un seuil.
      • Les codes d'erreur s'envolent.

Exemple de déroulement d'incident

  1. Alerte : « La délivrabilité Verizon a chuté de plus de 3 points sur la Grille US_PROMO_A. »
  2. Utiliser les journaux :
    • Vérifier les codes d'erreur et les volumes.
    • Comparer avec les autres grilles.
  3. Atténuer :
    • Déplacer temporairement le trafic promo Verizon vers la Grille US_PROMO_B.
    • Réduire le débit d'envoi.
  4. Investiguer :
    • Les changements récents de contenu/modèle de message.
    • Les modifications de la configuration de routage.

FAQ : le routage par correspondance opérateur pour les développeurs

1. Avons-nous besoin d'un lookup HLR pour chaque message ?

Pas nécessairement.

Options :

  • Mettre les résultats en cache pour une durée de vie (TTL) raisonnable.
  • Résoudre en amont pour les utilisateurs à fort trafic.
  • Effectuer des lookups par lots lors de l'amorçage des grilles.

2. Comment gérer la portabilité des numéros ?

Les numéros portés peuvent changer d'opérateur. Bonnes pratiques :

  • Actualiser périodiquement les informations opérateur pour :
    • Les destinations à haute fréquence.
    • Les numéros présentant des échecs répétés.

3. La correspondance opérateur n'est-elle pertinente qu'aux États-Unis ?

Non. Elle est particulièrement utile :

  • Partout où plusieurs opérateurs se comportent différemment.
  • Là où les sender IDs et les modèles de message sont spécifiques à chaque opérateur (de nombreux marchés d'Europe et d'Asie-Pacifique).

4. Comment cela s'articule-t-il avec l'A2P 10DLC et les campagnes enregistrées ?

La correspondance opérateur :

  • Utilise correctement les campagnes et expéditeurs enregistrés par opérateur.
  • Vous aide à rester dans les limites de débit et de contenu attendues par campagne.

5. Qu'en est-il de la confidentialité et des données personnelles (PII) ?

Une implémentation privilégiant la confidentialité :

  • Hache les MSISDN dans les journaux.
  • Stocke un minimum de données.
  • Conserve les métadonnées d'opérateur et de routage, pas le contenu brut.

6. Peut-on superposer la correspondance opérateur à un CPaaS existant ?

Parfois :

  • Si le CPaaS expose :
    • Des contrôles par opérateur.
    • Des statistiques par expéditeur.
  • Vous pouvez construire une couche de méta-routage par-dessus.

Mais les formes les plus robustes reposent sur une infrastructure propriétaire (cartes SIM, grilles privées).


Conclusion : du routage au mieux à un routage conçu comme une ingénierie

La plupart des programmes SMS fonctionnent sur un routage au mieux (best-effort) :

  • Le fournisseur choisit des routes bon marché/disponibles.
  • Vous obtenez une ou deux métriques.
  • Vous espérez que tout se passera bien.

Le routage par correspondance opérateur transforme le SMS en un système conçu comme une ingénierie :

  • Des choix de chemin déterministes par opérateur.
  • Des grilles et des pools isolés.
  • Une rotation et des replis tenant compte de la santé.
  • Une observabilité riche pour la gestion des incidents.

Si vous tenez à :

  • Atteindre et maintenir une délivrabilité de 99,4 % et plus.
  • Survivre aux pics promotionnels et aux cas d'usage à haut risque.
  • Donner à votre équipe SRE/infra des leviers qu'elle peut comprendre et en qui elle peut avoir confiance.

… alors mettre en place ou choisir une passerelle dotée d'une véritable architecture de correspondance opérateur n'est pas un simple plus, c'est la seule stratégie sensée à long terme.

Dach SMS Lab

Dach SMS Lab