Files

14 KiB
Raw Permalink Blame History

RelaisSMS

Vos contacts, votre agenda, vos SMS — au même endroit.

RelaisSMS est une petite application web auto-hébergée qui permet d'envoyer des SMS à des contacts ou à des groupes, immédiatement ou à une date choisie. Elle relie un carnet d'adresses CardDAV, un agenda CalDAV et l'API OVHcloud SMS pour centraliser les notifications et automatiser les rappels de rendez-vous.

Pensée à l'origine pour les besoins d'une association, elle propose une interface en français et fonctionne avec PHP et une base SQLite. Les contacts peuvent aussi être gérés directement dans l'application, sans serveur DAV.

Fonctionnalités

Domaine Ce que fait RelaisSMS
Contacts Fiches locales ou synchronisées par CardDAV, plusieurs numéros, normalisation au format international et détection des mobiles.
Groupes et favoris Sélection de contacts et de groupes, recherche et accès rapide aux favoris.
Préparation des envois Aperçu des destinataires, dédoublonnage des numéros, exclusion des contacts en refus SMS et limite de destinataires configurable.
Campagnes SMS Envoi immédiat ou programmé, annulation avant envoi, en-tête et pied de message configurables.
Rappels d'agenda Synchronisation CalDAV, alarmes de notification, événements récurrents, exceptions et annulations.
Suivi Historique des campagnes et de leurs destinataires, statuts détaillés, filtres et export CSV.
Envois incertains Rapprochement avec l'historique OVH après une interruption ou un délai dépassé, sans renvoi automatique.
Désinscription Refus SMS géré dans l'interface et prise en compte des réponses STOP via un callback OVH protégé par un jeton.
Exploitation Commandes CLI, tâches planifiées, suivi des synchronisations et purge de l'historique selon une durée configurable.

Intégrations disponibles : CardDAV et CalDAV, notamment avec Nextcloud, et OVHcloud SMS. Le connecteur Galette et les autres fournisseurs SMS restent des pistes d'évolution ; ils ne sont pas implémentés.

Comment ça fonctionne ?

  1. Ajoutez vos contacts ou synchronisez votre carnet CardDAV.
  2. Sélectionnez des contacts ou des groupes et vérifiez les numéros retenus.
  3. Rédigez votre message, puis envoyez-le immédiatement ou programmez-le.
  4. Consultez le résultat dans l'historique et exportez-le si nécessaire.

Pour les rappels automatiques, ajoutez un événement et une alarme de type notification dans votre agenda CalDAV. RelaisSMS retrouve les contacts correspondant aux adresses e-mail de l'organisateur et des participants, puis prépare les SMS à l'échéance de l'alarme. Des contacts ou groupes fixes peuvent compléter cette liste.

Stack et prérequis

  • PHP 8.3+, Composer et les extensions Laravel usuelles, dont PDO SQLite, mbstring, OpenSSL, XML, DOM, ctype et fileinfo.
  • Laravel 13, vues Blade et Tailwind CSS 4.
  • Node.js 20.19+ ou 22.12+ et npm pour construire les assets avec Vite 8.
  • SQLite par défaut ; les autres connexions Laravel nécessitent leur propre configuration et validation.
  • Un compte OVHcloud SMS avec des crédits et des identifiants API pour les envois réels.
  • Un serveur CardDAV et/ou CalDAV si vous souhaitez utiliser les synchronisations.

Installation locale

git clone https://git.afpma.pro/afpma/RelaisSMS.git
cd RelaisSMS
composer install
cp .env.example .env
touch database/database.sqlite
php artisan key:generate
php artisan migrate --seed
npm ci
npm run build
php artisan serve

Ouvrez ensuite http://127.0.0.1:8000/login.

Le compte de développement créé par le seeder est admin@relais-sms.test, avec le mot de passe relais-sms. Ces valeurs de démonstration sont réservées au développement. Définissez vos propres RELAIS_SMS_ADMIN_NAME, RELAIS_SMS_ADMIN_EMAIL et RELAIS_SMS_ADMIN_PASSWORD dans .env avant toute installation accessible au public. Le seeder refuse de créer un compte avec le mot de passe par défaut ou un mot de passe vide hors des environnements local et testing.

Le seeder ne change pas le mot de passe d'un compte déjà existant. Modifier .env puis relancer db:seed ne constitue donc pas une réinitialisation du mot de passe.

Sans identifiants OVH, l'application reste utilisable pour gérer les contacts et préparer les sélections ; les envois réels échoueront. Pour travailler sur l'interface, lancez npm run dev dans un second terminal. Pour exécuter les tâches planifiées localement :

php artisan schedule:work

Ce scheduler peut déclencher des envois si les intégrations sont configurées et si des campagnes ou rappels sont dus.

Configuration

Le fichier .env.example décrit les variables disponibles. Copiez-le en .env et renseignez vos secrets uniquement dans ce fichier, jamais dans le dépôt. Après une modification de configuration, exécutez php artisan config:clear.

SMS OVHcloud

RELAIS_SMS_ACTIVE_SMS_PROVIDER=ovh
OVH_ENDPOINT=ovh-eu
OVH_APPLICATION_KEY=
OVH_APPLICATION_SECRET=
OVH_CONSUMER_KEY=
OVH_SMS_SERVICE_NAME=
OVH_SMS_SENDER=
OVH_SMS_SHORT_CODE=true
OVH_SMS_NO_STOP_CLAUSE=true
OVH_SMS_STOP_CALLBACK_TOKEN=
OVH_SMS_TIMEOUT=20
OVH_SMS_SEND_TIMEOUT=60

Renseignez les clés et le service SMS dans .env. Les autorisations API utilisées sont :

  • GET /sms pour consulter les services ;
  • POST /sms/{serviceName}/jobs pour envoyer ;
  • GET /sms/{serviceName}/outgoing et GET /sms/{serviceName}/outgoing/* pour rapprocher les envois incertains.

Deux modes d'expéditeur sont proposés :

Mode Configuration Comportement
Numéro court avec réponse OVH_SMS_SHORT_CODE=true, expéditeur vide RelaisSMS retire les liens et domaines du texte envoyé. Les libellés des liens Markdown et HTML sont conservés.
Expéditeur personnalisé OVH_SMS_SHORT_CODE=false, OVH_SMS_SENDER renseigné Les liens sont conservés ; le nom d'expéditeur doit être accepté par OVH.

Un texte devenu vide après filtrage est refusé. L'historique conserve le message effectivement transmis. OVH_SMS_NO_STOP_CLAUSE contrôle la demande de clause STOP auprès du fournisseur ; adaptez ce réglage à la nature de vos messages et aux conditions du service.

Un délai dépassé ne signifie pas forcément que le SMS n'est pas parti. Les campagnes concernées passent en « À vérifier ». La commande suivante consulte l'historique OVH sans renvoyer de SMS :

php artisan sms-campaigns:reconcile

Elle traite aussi les campagnes bloquées en « Envoi » depuis plus de dix minutes. Une absence de résultat ne suffit pas à déclarer l'envoi en échec. Le statut « Envoyé » indique une acceptation par OVH, pas une confirmation de livraison au téléphone.

Pour les réponses STOP, générez un jeton aléatoire dédié dans OVH_SMS_STOP_CALLBACK_TOKEN, puis configurez chez OVH l'URL de callback STOP :

https://sms.example.org/ovh/sms/stop-callback?token=VOTRE_JETON

Le callback marque les contacts correspondant au numéro reçu en refus SMS. Le callback de changement d'état de livraison n'est pas implémenté.

Carnet d'adresses CardDAV

RELAIS_SMS_CARDDAV_SYNC_ENABLED=true
RELAIS_SMS_CARDDAV_URL=https://cloud.example.org/remote.php/dav/addressbooks/users/UTILISATEUR/CARNET/
RELAIS_SMS_CARDDAV_USERNAME=
RELAIS_SMS_CARDDAV_PASSWORD=

Utilisez de préférence un mot de passe d'application DAV. Une synchronisation peut être lancée depuis l'interface ou avec php artisan contacts:sync-carddav. Le scheduler la prévoit toutes les heures.

Agenda CalDAV et rappels

RELAIS_SMS_CALDAV_CALENDAR_SYNC_ENABLED=true
RELAIS_SMS_CALDAV_CALENDAR_URL=https://cloud.example.org/remote.php/dav/calendars/UTILISATEUR/CALENDRIER/
RELAIS_SMS_CALDAV_CALENDAR_USERNAME=
RELAIS_SMS_CALDAV_CALENDAR_PASSWORD=
RELAIS_SMS_CALDAV_CALENDAR_LOOKAHEAD_DAYS=90
RELAIS_SMS_CALDAV_SYNC_INTERVAL_MINUTES=60
RELAIS_SMS_CALDAV_DEFAULT_CONTACT_IDS=
RELAIS_SMS_CALDAV_DEFAULT_GROUP_IDS=
RELAIS_SMS_CALDAV_TIMEZONE=Europe/Paris
  • Seules les alarmes VALARM avec ACTION:DISPLAY déclenchent un SMS. Les alarmes e-mail sont ignorées.
  • L'organisateur et les participants sont rapprochés des contacts par leur adresse e-mail ; un mobile valide est nécessaire.
  • Les identifiants de contacts et groupes par défaut sont séparés par des virgules.
  • Les récurrences RRULE, les dates RDATE, les exclusions EXDATE, ainsi que les occurrences modifiées ou annulées sont prises en compte dans la fenêtre de synchronisation.
  • Chaque occurrence et son alarme disposent d'une protection contre les doublons. Les rappels déjà envoyés ou incertains ne sont pas renvoyés lors d'une synchronisation.
  • Un rappel tient dans un seul segment SMS : titre et date sont prioritaires, puis lieu et description selon la place disponible et l'encodage GSM ou Unicode.
  • Le préfixe, le suffixe et la longueur de description sont réglables avec les variables RELAIS_SMS_CALDAV_REMINDER_HEADER, RELAIS_SMS_CALDAV_REMINDER_FOOTER et RELAIS_SMS_CALDAV_DESCRIPTION_MAX_LENGTH.

Pour importer l'agenda sans traiter les envois :

php artisan calendar:sync-caldav

calendar:process-reminders traite les rappels dus et synchronise l'agenda si l'intervalle configuré est écoulé. Ses options --no-sync et --force-sync permettent respectivement d'ignorer ou de forcer cette synchronisation. Cette commande peut envoyer des SMS.

Tâches planifiées

Serveur avec cron à la minute

* * * * * cd /chemin/vers/RelaisSMS && php artisan schedule:run >> /dev/null 2>&1

Le scheduler vérifie chaque minute les rappels, les campagnes dues et les envois incertains. CardDAV est synchronisé toutes les heures ; les lectures CalDAV suivent l'intervalle configuré. L'historique expiré est purgé chaque jour à 02:30 selon le fuseau du scheduler Laravel.

Hébergement mutualisé OVHcloud

Si le planificateur ne permet qu'un passage horaire, configurez une seule tâche PHP 8.3 pointant vers RelaisSMS/cron.php (adaptez le chemin au dossier d'installation).

Ce point d'entrée CLI uniquement exécute explicitement, dans l'ordre : CardDAV, CalDAV et les rappels dus, les campagnes programmées, le rapprochement OVH, puis la purge de l'historique. Un simple schedule:run lancé à une minute quelconque de chaque heure pourrait manquer les tâches prévues à une heure précise.

La précision dépend du planificateur : avec un passage par heure, un envoi peut être retardé d'environ 0 à 60 minutes. Les commandes traitent par défaut jusqu'à 50 rappels ou 50 campagnes par passage ; un volume supérieur peut prolonger ce délai. Activez les rapports d'erreur du planificateur.

Déploiement et données privées

  1. Construisez les dépendances et les assets : composer install --no-dev --optimize-autoloader, npm ci, puis npm run build. Sur un hébergement sans Composer, transférez aussi vendor/ depuis un environnement compatible.
  2. Configurez le serveur web pour exposer uniquement public/. Si l'hébergeur impose une autre disposition, bloquez l'accès HTTP aux fichiers privés et conservez les fichiers de routage propres à l'installation.
  3. Créez le .env du serveur avec ses propres secrets et APP_ENV=production, APP_DEBUG=false, une APP_URL HTTPS et SESSION_SECURE_COOKIE=true.
  4. Sauvegardez le code remplacé, .env et SQLite avant toute mise à jour. Protégez les sauvegardes et .env avec des permissions restrictives, par exemple 600 ; autorisez l'écriture sur storage/ et bootstrap/cache/ au processus PHP.
  5. Passez en maintenance avec php artisan down, transférez le code et les assets, appliquez php artisan migrate --force, puis php artisan optimize:clear. Lors de la première installation uniquement, créez le compte avec php artisan db:seed --force après avoir défini son mot de passe.
  6. Remettez le service en ligne avec php artisan up, vérifiez la connexion et configurez le planificateur.

Conservez l'APP_KEY, les données et le jeton STOP lors des mises à jour. Sur l'installation mutualisée OVHcloud à l'origine du projet, n'activez pas config:cache ni artisan optimize : le cache de configuration y a provoqué une erreur HTTP 500. view:cache peut être utilisé séparément.

Le dépôt exclut les fichiers .env privés, bases locales, journaux, sessions, exports temporaires, sauvegardes et consignes locales d'exploitation. Ne publiez pas de véritables coordonnées dans des fixtures ou des captures d'écran. L'historique applicatif contient des données personnelles : sa rétention est réglée par RELAIS_SMS_HISTORY_RETENTION_YEARS (3 ans par défaut). Prévisualisez la purge avec :

php artisan sms-campaigns:prune-history --dry-run

Tests et contribution

php artisan test --compact
vendor/bin/pint --test
vendor/bin/phpstan analyse --memory-limit=1G --no-progress
npm run build

Les tests utilisent SQLite en mémoire ; les tests des intégrations utilisent des doublures HTTP. N'utilisez pas vos identifiants de production pour les tests. La commande sms:send-test effectue un envoi réel et consomme des crédits : ne l'exécutez qu'avec l'accord du destinataire et une autorisation explicite.

Les documents de conception et la maquette initiale sont dans doc/. Ils décrivent aussi des intentions historiques ; le tableau des fonctionnalités ci-dessus présente le périmètre actuellement implémenté.

Licence

RelaisSMS est distribué sous licence GNU GPL v3, conservée depuis l’initialisation du dépôt. Les dépendances restent soumises à leurs licences respectives.