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 ?
- Ajoutez vos contacts ou synchronisez votre carnet CardDAV.
- Sélectionnez des contacts ou des groupes et vérifiez les numéros retenus.
- Rédigez votre message, puis envoyez-le immédiatement ou programmez-le.
- 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 /smspour consulter les services ;POST /sms/{serviceName}/jobspour envoyer ;GET /sms/{serviceName}/outgoingetGET /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
VALARMavecACTION:DISPLAYdé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 datesRDATE, les exclusionsEXDATE, 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_FOOTERetRELAIS_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
- Construisez les dépendances et les assets :
composer install --no-dev --optimize-autoloader,npm ci, puisnpm run build. Sur un hébergement sans Composer, transférez aussivendor/depuis un environnement compatible. - 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. - Créez le
.envdu serveur avec ses propres secrets etAPP_ENV=production,APP_DEBUG=false, uneAPP_URLHTTPS etSESSION_SECURE_COOKIE=true. - Sauvegardez le code remplacé,
.envet SQLite avant toute mise à jour. Protégez les sauvegardes et.envavec des permissions restrictives, par exemple600; autorisez l'écriture surstorage/etbootstrap/cache/au processus PHP. - Passez en maintenance avec
php artisan down, transférez le code et les assets, appliquezphp artisan migrate --force, puisphp artisan optimize:clear. Lors de la première installation uniquement, créez le compte avecphp artisan db:seed --forceaprès avoir défini son mot de passe. - 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.