npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smsmode/rcs

v1.0.0

Published

Official TypeScript SDK for smsmode© REST API RCS

Readme

@smsmode/rcs

SDK TypeScript officiel pour l'API REST smsmode© : Intégrez la messagerie RCS enrichie en moins de 5 minutes.

npm Node.js


Présentation

Ce SDK est une couche d'abstraction au-dessus de l'API REST smsmode©. Il vous évite de gérer manuellement les headers HTTP, l'encodage des paramètres, les erreurs et le timeout, et vous offre à la place une interface TypeScript typée avec autocomplétion IDE complète.

Ce que le SDK prend en charge :

  • Envoi de messages RCS unitaires, en batch et programmés
  • Contenu riche : texte, fichier, carte et carrousel avec suggestions interactives
  • Gestion des campagnes RCS groupées (jusqu'à 1 000 destinataires)
  • Réception et identification des webhooks (DLR et MO)
  • Gestion des erreurs typées (400, 401, 402, 429, etc.)
  • Timeout configurable avec annulation propre via AbortController

Ce que le SDK ne prend pas en charge :

  • Retry automatique : c'est intentionnel. La stratégie de retry dépend de votre contexte métier. Un exemple est fourni dans la section Patterns.
  • Gestion des channels et organisations : utilisez l'interface smsmode ou l'API directement.

Installation

npm install @smsmode/rcs

Prérequis : Node.js >= 20.0.0

Le SDK utilise fetch natif (disponible nativement depuis Node.js 18, sans flag depuis Node.js 20) et n'a aucune dépendance runtime. Rien d'autre à installer.

ESM (recommandé)

Si votre projet utilise "type": "module" dans son package.json ou des fichiers .mjs, utilisez l'import ES module :

import { SmsmodeRcsClient } from '@smsmode/rcs';

CommonJS

Si votre projet utilise require() (fichiers .js sans "type": "module", ou .cjs), utilisez :

const { SmsmodeRcsClient } = require('@smsmode/rcs');

Le package expose les deux formats : vous n'avez rien à configurer, Node.js choisit automatiquement le bon selon votre environnement.


⚠️ Server-side uniquement. Ce SDK est conçu pour s'exécuter côté serveur (Node.js). Ne l'utilisez jamais dans du code exécuté dans un navigateur : votre clé API serait exposée publiquement dans le bundle JavaScript.


Quick Start

L'exemple minimal pour envoyer votre premier message RCS :

import { SmsmodeRcsClient } from '@smsmode/rcs';

const client = new SmsmodeRcsClient({ apiKey: 'your-api-key' });

const message = await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Bonjour depuis smsmode RCS !' },
});

console.log(message.messageId);    // identifiant unique du message
console.log(message.status.value); // "ENROUTE", "DELIVERED"...

Pensez à envelopper vos appels dans un try/catch en production : voir la section Gestion des erreurs.


Configuration

const client = new SmsmodeRcsClient({
  apiKey: 'your-api-key', // Obligatoire
  timeout: 10000,          // Optionnel — défaut : 10 000ms
});

apiKey : Votre clé API smsmode. Voir ci-dessous comment l'obtenir et la gérer.

timeout : Durée maximale d'attente d'une réponse en millisecondes. Au-delà, la requête est annulée et une erreur est levée. Augmentez cette valeur si vous envoyez des batchs volumineux.

Obtenir et gérer votre clé API

Où la trouver :

  1. Connectez-vous à votre espace client smsmode
  2. Allez dans Settings > API Keys
  3. Créez une nouvelle clé ou copiez une clé existante

Bonnes pratiques :

Ne commitez jamais votre clé API dans votre dépôt Git. Utilisez une variable d'environnement :

# fichier .env à la racine du projet
SMSMODE_API_KEY=votre_cle_api
// Chargez la variable avec dotenv ou votre gestionnaire d'environnement
const client = new SmsmodeRcsClient({ apiKey: process.env.SMSMODE_API_KEY });

Permissions associées à une clé :

Chaque clé API est liée à un rôle dans votre organisation smsmode :

| Rôle | Périmètre | |------|-----------| | User | Envoyer dans son propre Channel uniquement | | Manager | Envoyer dans tous les Channels de son organisation | | Administrator | Envoyer dans les Channels des sous-organisations |

Si vous recevez une erreur 401, vérifiez que :

  • La clé est bien copiée en entier (sans espace avant/après)
  • La clé n'a pas été révoquée dans l'espace client
  • Vous utilisez bien le header X-Api-Key : le SDK le gère automatiquement, mais si vous testez via curl ou Postman, c'est un point d'attention courant

Enregistrer votre agent RCS

En plus de votre clé API, l'envoi de messages RCS nécessite un agent RCS validé. Un agent RCS est l'identité d'entreprise vérifiée qui apparaît côté destinataire : nom de marque, logo, couleur. À la différence d'un expéditeur SMS libre, un agent RCS doit être enregistré et validé par Google avant tout envoi.

Prérequis bloquant : aucun message ne peut être envoyé sans agent validé. Cette étape est à effectuer une seule fois, avant toute intégration.

Enregistrer mon agent RCS →

Une fois votre agent validé par Google et les opérateurs, il est automatiquement utilisé pour tous vos envois. Si vous disposez de plusieurs agents et souhaitez en sélectionner un spécifique, renseignez le champ from dans votre requête avec le nom de l'agent tel qu'il apparaît dans la liste des agents de votre Channel (1-40 caractères).


Format des numéros de téléphone

Tous les numéros doivent être au format E.164 : indicatif pays suivi du numéro local, sans +, sans espaces, sans tirets, sans zéro initial.

| Pays | Numéro local | Format E.164 | |------|-------------|--------------| | France | 06 00 00 00 01 | 33600000001 | | Belgique | 0499 00 00 01 | 32499000001 | | Suisse | 079 000 00 01 | 41790000001 |

// Correct
{ to: '33600000001' }

// Correct également : le + est accepté
{ to: '+33600000001' }

// Incorrect : le zéro initial du numéro local ne s'inclut pas
{ to: '0600000001' }

// Incorrect : pas d'espaces ni de tirets
{ to: '33 6 00 00 00 01' }

Un numéro mal formaté entraînera une erreur 400 Bad Request de l'API.

Note RCS : le destinataire doit disposer d'un appareil et d'un opérateur compatibles RCS. Si ce n'est pas le cas, le message ne sera pas livré : RCS ne dispose pas de fallback SMS automatique.


Messages RCS

Un Message est un envoi RCS unitaire ou en batch. C'est la ressource de base de l'API smsmode RCS. Chaque message est lié à un Channel (votre agent RCS) et peut être associé à une Campaign pour le suivi statistique.

Envoyer un message

Envoi simple — le minimum requis.

await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Bonjour depuis smsmode RCS !' },
});

Avec un agent RCS personnalisé. Le champ from permet de sélectionner l'agent parmi la liste de votre Channel (1-40 caractères, défaut : "Default RCS Agent").

await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Votre commande a été expédiée.' },
  from: 'MonBrand',
});

Message programmé. Renseignez sentDate pour programmer l'envoi. Le message peut être modifié ou annulé avant l'envoi via update() ou cancel().

await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Rappel : votre rendez-vous est demain à 10h.' },
  sentDate: '2026-06-01T10:00:00Z',
});

Avec validité personnalisée. Si le message ne peut pas être livré dans ce délai, il expire avec le statut UNDELIVERED.

await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Votre code : 123456' },
  validity: { amount: 30, timeUnit: 'SECONDS' }, // min 30s, max 48h (défaut)
});

Avec référence client et callbacks. callbackUrlStatus reçoit les accusés de réception (DLR), callbackUrlMo reçoit les réponses entrantes.

await client.send({
  recipient: { to: '33600000001' },
  body: { type: 'TEXT', text: 'Votre message' },
  refClient: 'commande-42',
  callbackUrlStatus: 'https://votre-serveur.com/webhook/dlr',
  callbackUrlMo: 'https://votre-serveur.com/webhook/mo',
});

Envoi en batch. Jusqu'à 1 000 messages en un seul appel. Chaque message est indépendant. Les overloads TypeScript infèrent automatiquement RcsMessage pour un envoi unitaire et RcsMessage[] pour un batch.

const results = await client.send([
  { recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Bonjour Alice !' } },
  { recipient: { to: '33600000002' }, body: { type: 'TEXT', text: 'Bonjour Bob !' } },
]);

Pour envoyer à plus de 1 000 destinataires, utilisez les Campagnes ou découpez en plusieurs batchs.

Lister les messages

// Retourne la première page avec les paramètres par défaut
const result = await client.list();

console.log(result.totalCount); // nombre total de messages (toutes pages)
console.log(result.count);      // nombre de messages dans cette page
console.log(result.items);      // tableau des messages de la page courante

// Avec pagination et filtres
const filtered = await client.list({
  page: 1,
  pageSize: 20,
  searchBy: {
    direction: 'MT',       // MT = envoyé, MO = reçu
    to: '33600000001',     // filtrer par destinataire
  },
  sortBy: { sentDate: 'DESC' }, // plus récents en premier
});

Obtenir un message

const message = await client.get('67c15045-1067-4588-ba3c-737cc5051438');

Modifier un message programmé

Un message programmé peut être modifié tant qu'il n'a pas encore été envoyé (et au moins 5 minutes avant l'heure d'envoi). Seuls les champs passés dans le payload sont mis à jour, les autres restent inchangés.

await client.update('67c15045-1067-4588-ba3c-737cc5051438', {
  sentDate: '2026-06-02T09:00:00Z',
  refClient: 'commande-99',
});

Effacer un champ optionnel. Passer une chaîne vide pour retirer un paramètre.

await client.update('67c15045-1067-4588-ba3c-737cc5051438', {
  refClient: '',
});

Annuler un message programmé

Important : cancel() ne fonctionne que sur les messages programmés (statut SCHEDULED). Un message déjà parti ne peut pas être annulé.

await client.cancel('67c15045-1067-4588-ba3c-737cc5051438');

Utiliser un Channel ou une Campaign spécifique

Par défaut, les appels utilisent le Channel lié à votre clé API. Vous pouvez cibler un Channel ou une Campaign spécifique via le second paramètre options, disponible sur toutes les méthodes.

// Envoyer via un Channel spécifique (nécessite les permissions Manager ou Administrator)
await client.send(
  { recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
  { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' }
);

// Associer le message à une Campaign existante (pour les statistiques)
await client.send(
  { recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
  { campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70' }
);

// Combiner les deux
await client.send(
  { recipient: { to: '33600000001' }, body: { type: 'TEXT', text: 'Hello !' } },
  {
    channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd',
    campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70',
  }
);

// Le second paramètre options fonctionne sur toutes les méthodes
await client.list({ page: 1 }, { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });
await client.get('message-id', { campaignId: '4c9f9589-1ffd-48da-82d2-65aa9e5f5f70' });
await client.update('message-id', { sentDate: '2026-06-02T09:00:00Z' }, { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });
await client.cancel('message-id', { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' });

Campagnes RCS

Une Campaign est un envoi groupé à plusieurs destinataires, traité comme une unité cohérente avec des statistiques consolidées. C'est la ressource recommandée pour les communications marketing ou les notifications de masse.

Différence avec le batch de Messages :

  • Le batch (client.send([...])) envoie des messages indépendants sans lien entre eux.
  • La Campaign regroupe tous les envois sous un identifiant commun (campaignId), ce qui permet de suivre les performances globales.

Limite : 1 000 destinataires par campagne. Pour dépasser cette limite, créez la campagne d'abord, puis envoyez des messages supplémentaires via client.send([...], { campaignId }).

Envoyer une campagne

Envoi simple — le minimum requis.

const campaign = await client.campaigns.send({
  name: 'Soldes de printemps 2026',
  recipients: [
    { to: '33600000001' },
    { to: '33600000002' },
  ],
  body: { type: 'TEXT', text: 'Profitez de nos offres exclusives ce week-end !' },
});

console.log(campaign.campaignId); // identifiant de la campagne
console.log(campaign.status);     // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
console.log(campaign.quantity);   // nombre de destinataires

Avec contenu riche. Le champ body accepte tous les types RCS : BASIC, TEXT, FILE, CARD, CAROUSEL.

await client.campaigns.send({
  name: 'Catalogue printemps',
  recipients: [{ to: '33600000001' }, { to: '33600000002' }],
  body: {
    type: 'CAROUSEL',
    cardWidth: 'MEDIUM',
    contents: [
      {
        title: 'Nouveauté A',
        media: { fileUrl: 'https://example.com/a.jpg' },
        suggestions: [{ type: 'REPLY', text: 'En savoir plus', postbackData: 'plus_a' }],
      },
      {
        title: 'Nouveauté B',
        media: { fileUrl: 'https://example.com/b.jpg' },
        suggestions: [{ type: 'REPLY', text: 'En savoir plus', postbackData: 'plus_b' }],
      },
    ],
  },
});

Campagne programmée. Renseignez sentDate pour programmer l'envoi (au minimum 30 minutes dans le futur).

await client.campaigns.send({
  name: 'Rappel rendez-vous',
  recipients: [{ to: '33600000001' }],
  body: { type: 'TEXT', text: 'Rappel : votre rendez-vous est demain.' },
  sentDate: '2026-04-20T09:00:00Z',
});

Via un Channel spécifique. Nécessite les permissions Manager ou Administrator.

await client.campaigns.send(
  { name: 'Promo', recipients: [{ to: '33600000001' }], body: { type: 'TEXT', text: 'Hello !' } },
  { channelId: 'da0e501d-4449-40b1-b1f9-3cd1e94031bd' }
);

Lister les campagnes

Retourne la première page avec les paramètres par défaut.

const result = await client.campaigns.list();

console.log(result.totalCount); // nombre total de campagnes
console.log(result.items);      // campagnes de la page courante

Avec filtres.

const scheduled = await client.campaigns.list({
  searchBy: { status: 'SCHEDULED' }, // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
  sortBy: { sentDate: 'DESC' },
});

Obtenir une campagne

const campaign = await client.campaigns.get('67c15045-1067-4588-ba3c-737cc5051438');

console.log(campaign.status);    // "SCHEDULED" | "ONGOING" | "ENDED" | "CANCELED"
console.log(campaign.quantity);  // nombre total de destinataires
console.log(campaign.statuses);  // répartition des statuts de livraison

Modifier une campagne programmée

Les modifications s'appliquent à tous les messages unitaires de la campagne.

await client.campaigns.update('67c15045-1067-4588-ba3c-737cc5051438', {
  name: 'Soldes printemps v2',
  sentDate: '2026-04-21T10:00:00Z',
});

Annuler une campagne programmée

Important : update() et cancel() ne fonctionnent que sur les campagnes programmées (statut SCHEDULED). Une campagne déjà en cours ou terminée ne peut pas être modifiée ni annulée.

await client.campaigns.cancel('67c15045-1067-4588-ba3c-737cc5051438');

Contenu riche

RCS supporte cinq types de contenu, tous typés via l'union discriminée RcsBody. Le champ type détermine la structure exacte du corps : TypeScript vous guide à chaque étape grâce à l'autocomplétion.

BASIC : texte brut (max 160 caractères, sans suggestions)

Idéal pour les notifications transactionnelles simples.

body: { type: 'BASIC', text: 'Votre colis a été livré.' }

TEXT : texte enrichi avec suggestions (max 3 072 caractères, jusqu'à 11 suggestions)

Permet d'ajouter des boutons d'action sous le message.

body: {
  type: 'TEXT',
  text: 'Choisissez un créneau pour votre livraison :',
  suggestions: [
    { type: 'REPLY', text: 'Matin', postbackData: 'creneau_matin' },
    { type: 'REPLY', text: 'Après-midi', postbackData: 'creneau_aprem' },
    { type: 'OPEN_URL', text: 'Voir le suivi', postbackData: 'suivi', url: 'https://example.com/suivi' },
  ],
}

FILE : image, vidéo, audio ou PDF

// Image
body: {
  type: 'FILE',
  fileUrl: 'https://example.com/banniere.jpg',
}

// Vidéo avec miniature
body: {
  type: 'FILE',
  fileUrl: 'https://example.com/presentation.mp4',
  thumbnailUrl: 'https://example.com/miniature.jpg',
}

CARD : carte enrichie avec média, titre et suggestions

body: {
  type: 'CARD',
  orientation: 'VERTICAL', // ou "HORIZONTAL"
  content: {
    title: 'Soldes de printemps',
    description: 'Jusqu\'à -50% ce week-end seulement.',
    media: { fileUrl: 'https://example.com/banniere.jpg', height: 'MEDIUM' },
    suggestions: [
      { type: 'OPEN_URL', text: 'Voir les offres', postbackData: 'voir', url: 'https://example.com/soldes' },
      { type: 'REPLY', text: 'Me rappeler', postbackData: 'rappel' },
    ],
  },
  suggestions: [
    { type: 'REPLY', text: 'Non merci', postbackData: 'non' },
  ],
}

CAROUSEL : de 2 à 11 cartes défilantes

Idéal pour présenter plusieurs produits ou offres dans un seul message.

body: {
  type: 'CAROUSEL',
  cardWidth: 'MEDIUM', // ou "SMALL"
  contents: [
    {
      title: 'Produit A',
      description: '29,99 €',
      media: { fileUrl: 'https://example.com/produit-a.jpg' },
      suggestions: [
        { type: 'REPLY', text: 'Ajouter au panier', postbackData: 'ajout_a' },
      ],
    },
    {
      title: 'Produit B',
      description: '49,99 €',
      media: { fileUrl: 'https://example.com/produit-b.jpg' },
      suggestions: [
        { type: 'REPLY', text: 'Ajouter au panier', postbackData: 'ajout_b' },
      ],
    },
  ],
}

Types de suggestions

Les suggestions peuvent être placées à deux niveaux :

  • Dans le contenu d'une carte (content.suggestions pour CARD, contents[].suggestions pour chaque carte d'un CAROUSEL) : max 4 suggestions. Ces boutons sont attachés directement à la carte.
  • Sous l'ensemble du message (body.suggestions pour TEXT, CARD et CAROUSEL) : max 11 suggestions. Ces boutons apparaissent en dessous du contenu principal.

Tous les types de suggestions partagent trois champs de base : type (identifiant du type), text (texte affiché sur le bouton, max 25 caractères) et postbackData (payload renvoyé à l'agent quand l'utilisateur appuie, max 2048 caractères, encodé en base64). La colonne "Champs supplémentaires" liste uniquement les champs propres à chaque type.

| Type | Description | Champs supplémentaires | |------|-------------|------------------------| | REPLY | Réponse rapide prédéfinie | aucun | | OPEN_URL | Ouvre une URL | url, webviewSize? | | DIAL_PHONE | Lance un appel | phoneNumber | | SHOW_LOCATION | Affiche une position sur la carte | latitude, longitude, label? | | REQUEST_LOCATION | Demande la position du destinataire | aucun | | CREATE_CALENDAR_EVENT | Crée un événement calendrier | startTime, endTime, title, description? |

REPLY

{ type: 'REPLY', text: 'Confirmer', postbackData: 'confirm' }

OPEN_URL

{ type: 'OPEN_URL', text: 'Voir les offres', postbackData: 'offres', url: 'https://example.com/offres' }

DIAL_PHONE

{ type: 'DIAL_PHONE', text: 'Appeler le support', postbackData: 'support', phoneNumber: '33800000000' }

SHOW_LOCATION

{ type: 'SHOW_LOCATION', text: 'Notre magasin', postbackData: 'magasin', latitude: 48.8566, longitude: 2.3522 }

REQUEST_LOCATION

{ type: 'REQUEST_LOCATION', text: 'Partager ma position', postbackData: 'location' }

CREATE_CALENDAR_EVENT

{
  type: 'CREATE_CALENDAR_EVENT',
  text: 'Ajouter à mon agenda',
  postbackData: 'agenda',
  title: 'Rendez-vous smsmode',
  startTime: '2026-06-01T10:00:00Z',
  endTime: '2026-06-01T11:00:00Z',
}

Templates RCS

smsmode propose une bibliothèque de templates RCS prêts à l'emploi (notifications de livraison, promotions, flows de discussion...) pour vous aider à démarrer.

Découvrir les templates RCS smsmode →


Webhooks

Les webhooks sont des requêtes de callback que smsmode envoie vers votre serveur pour vous informer d'événements liés à vos messages RCS.

Il existe deux types de notifications :

DLR (Delivery Report) : smsmode vous notifie chaque fois que le statut d'un message RCS change (envoyé, livré, lu, en erreur...). Configuré via callbackUrlStatus.

MO (Mobile Originated) : smsmode vous notifie quand un utilisateur répond à l'un de vos messages RCS. Le corps du message MO est systématiquement de type TEXT. Configuré via callbackUrlMo.

Les deux types partagent la même structure de base (ressource Message) et se distinguent par le champ direction : MT pour un DLR, MO pour un message entrant. Le champ status n'est présent que dans un DLR.

Configurer l'URL de réception

  1. Global (recommandé) : via l'interface smsmode sous Settings > Webhooks. C'est la façon la plus simple : vous configurez une URL une seule fois et elle s'applique à tous vos envois, sans toucher au code.
  2. Par channel : via l'API Channel, si vous avez plusieurs channels et souhaitez une URL différente par channel.
  3. Par message : champs callbackUrlStatus (DLR) et callbackUrlMo (MO) directement dans le send(), pour surcharger ponctuellement la configuration globale.

Mécanisme de retry

Si votre endpoint ne répond pas avec un statut 2xx, smsmode retente la notification jusqu'à 6 fois :

| Tentative | Délai depuis la précédente | |-----------|---------------------------| | 1 | 30 secondes | | 2 | 2 minutes | | 3 | 10 minutes | | 4 | 1 heure | | 5 | 5 heures | | 6 | 24 heures |

Après la 6ème tentative, la notification est abandonnée. Votre endpoint doit gérer les doublons en dédupliquant sur messageId.

Recevoir et identifier un webhook

Le SDK expose trois utilitaires pour traiter les webhooks de façon typée :

  • parseWebhookPayload(body) : valide que le payload est bien formé et retourne un type discriminé
  • isDeliveryReport(payload) : type guard : retourne true si direction === 'MT'
  • isIncomingMessage(payload) : type guard : retourne true si direction === 'MO'
import express from 'express';
import {
  parseWebhookPayload,
  isDeliveryReport,
  isIncomingMessage,
} from '@smsmode/rcs';

const app = express();
app.use(express.json());

app.post('/webhook/rcs', (req, res) => {
  try {
    const payload = parseWebhookPayload(req.body);

    if (isDeliveryReport(payload)) {
      // Rapport de livraison (DLR) : direction: 'MT'
      // payload.status.value : "ENROUTE", "DELIVERED", "READ", "UNDELIVERABLE"...
      console.log(`Message ${payload.messageId} : statut : ${payload.status.value}`);

    } else if (isIncomingMessage(payload)) {
      // Message entrant (MO) : direction: 'MO'
      // payload.body est toujours de type RcsTextBody
      console.log(`Réponse de ${payload.recipient.to} : ${payload.body.text}`);

      // originMessageId identifie le message MT auquel ce MO répond
      console.log(`En réponse au message : ${payload.originMessageId}`);
    }

    // Toujours répondre avec un statut 2xx pour acquitter la notification
    // Sans cela, smsmode considérera la notification comme échouée et retentera
    res.sendStatus(200);

  } catch {
    // parseWebhookPayload lève une ValidationError si le payload est invalide
    res.sendStatus(400);
  }
});

Patterns

Pagination complète

La réponse paginée contient totalCount (total global) et items (page courante). Pour parcourir toutes les pages :

let page = 1;
let hasMore = true;
const allMessages: RcsMessage[] = [];

while (hasMore) {
  const result = await client.list({ page, pageSize: 100 });

  allMessages.push(...result.items);

  // S'il y a moins d'items que la taille de page, on est sur la dernière page
  hasMore = result.items.length === result.pageSize;
  page++;
}

console.log(`${allMessages.length} messages récupérés`);

Gestion du rate limit

Le SDK lève une RateLimitError sur les réponses HTTP 429. L'erreur expose retryAfter, la durée d'attente recommandée en secondes fournie par l'API. Vous pouvez implémenter un retry automatique selon votre besoin :

import { RcsSendPayload, RateLimitError } from '@smsmode/rcs';

async function sendWithRetry(payload: RcsSendPayload, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.send(payload);
    } catch (error) {
      if (error instanceof RateLimitError && attempt < maxRetries - 1) {
        const waitMs = (error.retryAfter ?? 60) * 1000;
        await new Promise(resolve => setTimeout(resolve, waitMs));
        continue;
      }
      throw error; // autres erreurs ou dernière tentative : on laisse remonter
    }
  }
}

Gestion des erreurs

Crédit insuffisant : client.send() ne lève pas d'erreur quand le crédit est insuffisant. L'API répond 200 avec le statut ENROUTE, puis vous envoie un DLR UNDELIVERABLE. Si vous ne traitez pas vos DLR, vous ne le saurez jamais.

Le SDK transforme chaque réponse d'erreur HTTP en une classe TypeScript typée. La distinction centrale est la présence ou non d'un body d'erreur structuré smsmode dans la réponse, pas le code HTTP lui-même. Cela détermine ce que vous pouvez réellement faire avec l'erreur.

Hiérarchie des classes d'erreur :

Error
└── RcsError
    ├── SmsModeApiError      : body smsmode structuré présent
    │   ├── AuthError          : HTTP 401
    │   └── RateLimitError     : HTTP 429 (+ retryAfter)
    └── SmsModeHttpError     : pas de body structuré (5xx, gateway timeout, HTML d'un reverse proxy)
import {
  SmsmodeRcsClient,
  SmsModeApiError,
  SmsModeHttpError,
  AuthError,
  RateLimitError,
} from '@smsmode/rcs';

const client = new SmsmodeRcsClient({ apiKey: process.env.SMSMODE_API_KEY });

try {

  await client.send({
    recipient: { to: '33600000001' },
    body: { type: 'TEXT', text: 'Bonjour !' },
  });
  console.log(message.messageId);
  console.log(message.status.value);

} catch (error) {
  if (error instanceof AuthError) {
    // Clé API invalide : vérifier process.env.SMSMODE_API_KEY
    console.error('Clé API invalide');

  } else if (error instanceof RateLimitError) {
    // Trop de requêtes : attendre retryAfter secondes
    const wait = error.retryAfter ?? 60;
    console.error(`Rate limit atteint, retry dans ${wait}s`);

  } else if (error instanceof SmsModeApiError) {
    console.error("   httpStatus :", error.httpStatus);
    console.error("   errorCode  :", error.errorCode);
    console.error("   message    :", error.message);
    console.error("   detail     :", error.detail);
    console.error("   docsUrl    :", error.docsUrl);

    // Branchement fin possible sur error.errorCode :
    switch (error.errorCode) {
      case '400.029': // format E.164 invalide
      // etc.
    }

  } else if (error instanceof SmsModeHttpError) {
    // Pas de body structuré : 5xx, gateway timeout, reverse proxy
    console.error(`HTTP ${error.httpStatus} ${error.statusText} : retry recommandé`);

  } else {
    throw error;
  }
}

Classes d'erreur exposées par le SDK :

| Classe | Quand ? | Propriétés | |--------|---------|------------| | SmsModeApiError | L'API smsmode a répondu avec un body d'erreur structuré (4xx principalement) | httpStatus, title, message, detail, errorCode, docsUrl, details | | AuthError | HTTP 401, clé API invalide | Hérite de SmsModeApiError | | RateLimitError | HTTP 429 avec body structuré | Hérite de SmsModeApiError + retryAfter?: number | | SmsModeHttpError | Réponse HTTP en erreur sans body structuré (5xx, gateway timeout, HTML d'un reverse proxy) | httpStatus, statusText |

Propriétés de SmsModeApiError :

| Propriété | Type | Description | |-----------|------|-------------| | httpStatus | number | Code HTTP (ex : 400) | | title | string | Titre HTTP humain (ex : "Bad Request") | | message | string | Description courte de l'erreur | | detail | string | Contrainte précise non respectée | | errorCode | string | Code métier smsmode (ex : "400.029") | | docsUrl | string | URL vers la documentation du code d'erreur | | details | unknown | Body brut complet de la réponse |

Tous les champs de SmsModeApiError sont garantis non-undefined. La présence du body structuré smsmode est vérifiée avant de lancer cette classe.

Pour la liste exhaustive des codes d'erreur métier smsmode, consultez la documentation officielle des codes de statut.


Référence API

SmsmodeRcsClient

| Option | Type | Défaut | Description | |--------|------|--------|-------------| | apiKey | string | — | Obligatoire. Clé API smsmode | | timeout | number | 10000 | Timeout en millisecondes |

client : Messages

| Méthode | Paramètres | Retour | Description | |---------|------------|--------|-------------| | send(payload, options?) | RcsSendPayload \| RcsSendPayload[] | RcsMessage \| RcsMessage[] | Envoyer un message unitaire ou batch (max 1 000) | | list(params?, options?) | RcsListParams | PaginatedResponse<RcsMessage> | Lister les messages avec filtres et pagination | | get(messageId, options?) | string | RcsMessage | Obtenir un message par son ID | | update(messageId, payload, options?) | string, RcsUpdatePayload | RcsMessage | Modifier un message programmé | | cancel(messageId, options?) | string | void | Annuler un message programmé |

client.campaigns : Campagnes

| Méthode | Paramètres | Retour | Description | |---------|------------|--------|-------------| | send(payload, options?) | RcsCampaignSendPayload | RcsCampaign | Envoyer ou programmer une campagne (max 1 000 destinataires) | | list(params?, options?) | RcsCampaignListParams | PaginatedResponse<RcsCampaign> | Lister les campagnes avec filtres et pagination | | get(campaignId, options?) | string | RcsCampaign | Obtenir une campagne par son ID | | update(campaignId, payload, options?) | string, RcsCampaignUpdatePayload | RcsCampaign | Modifier une campagne programmée | | cancel(campaignId, options?) | string | void | Annuler une campagne programmée |

Webhooks

| Fonction | Paramètres | Retour | Description | |----------|------------|--------|-------------| | parseWebhookPayload(body) | unknown | RcsWebhookPayload | Valide et type-asserte un payload entrant. Lève une ValidationError si invalide. | | isDeliveryReport(payload) | RcsWebhookPayload | boolean | Type guard : true si DLR (direction: 'MT') | | isIncomingMessage(payload) | RcsWebhookPayload | boolean | Type guard : true si MO (direction: 'MO') |


Ressources


Changelog

Voir CHANGELOG.md pour l'historique des versions.


Licence

MIT — voir LICENSE