Text Notifications

Text Notifications

Submitted

May 5, 2026 

Approved

 

Status

IN REVIEW

Impact

MEDIUM

Arch Ticket

https://folio-org.atlassian.net/browse/ARCH-351

Prod ticket

https://folio-org.atlassian.net/browse/UXPROD-1532

Executive Summary

  • Purpose: Enable SMS notifications for patrons via the existing app-notifications flow in FOLIO.

  • Approach: Introduce a new backend module, mod-sms, that abstracts SMS sending and integrates with external providers.

  • Providers: Support pluggable connectors for AWS SNS, Vonage, and Twilio; choose per-tenant via configuration.

  • Scope (initial): Outbound SMS only triggered by app-notifications; message templating and localization reused from existing notifications.

Requirements

Functional Requirements

  • Folio platform is able to send Text (SMS) notifications using app-notifications modules

  • one-way notifications (from Folio to patrons)

Non-Functional Requirements

  • Notification is send in (N) ms

  • Notifications is reliably delivered to the user

Patron Notification Proposal

mod-sms-proposal-20260722-124309.png
@startuml patron-notices-immediate-sequence title mod-circulation – Immediate Patron Notice\n(Checkout / Checkin / Renew / Request events) skinparam defaultFontSize 13 skinparam sequenceMessageAlign center participant "mod-circulation" as modCirc #moccasin participant "mod-circulation-storage" as modCircStorage #moccasin queue "Apache Kafka" as kafka #moccasin participant "mod-notify" as modNotify #darkSeaGreen participant "mod-template-engine" as modTemplateEngine #darkSeaGreen participant "mod-sender" as modSender #darkSeaGreen participant "mod-event-config" as modEventConfig #darkSeaGreen participant "mod-email" as modEmail #darkSeaGreen participant "mod-sms" as modSms #hotPink participant "mod-batch-print" as modBatchPrint #darkSeaGreen participant "mod-users" as modUsers #moccasin [-> modCirc ++ #moccasin : Perform circulation operation\n(Checkout / Checkin / Renew / Request events) modCirc -> modCirc: //Execute business logic; build PatronNoticeEvent// modCirc -> modCircStorage ++ #moccasin : //GET /patron-notice-policy-storage/patron-notice-policies// modCirc <-- modCircStorage -- : //found PatronNoticePolicies (can be empty)// modCirc -> modCirc : //find matching NoticeConfiguration for event type; build PatronNotice// alt NoticeConfiguration found modCirc -> modNotify ++ #darkSeaGreen : //POST /patron-notice// note right of modCirc { "recipientId": <uuid>, "templateId": <uuid>, "deliveryChannel": "email|sms|print", "context": { <loan|request|item|user> } } end note modNotify -> modEventConfig ++ #darkSeaGreen: //GET /event-config// modNotify <-- modEventConfig -- : //200 OK [Message Body]// modNotify -> modTemplateEngine ++ #darkSeaGreen: // POST /template-request // note right of modNotify { "templateId": <uuid>, "lang": <string>, "outputFormat": <string>, "context": { <loan|request|item|user> } } end note modNotify <-- modTemplateEngine -- : //200 OK [Message Body]// modNotify -> modSender ++ #darkSeaGreen : //POST /message-delivery// note right of modNotify { "notificationId": <uuid>, "recipientUserId": <uuid>, "messages": [ { "deliveryChannel": <email|sms|print>, "from": <string>, "to": <string>, "header": <string>, "body": <string>, "outputFormat": <string>, "attachments": [ { "contentType": <string>, "name": <string>, "description": <string>, "data": <string>, "disposition": <string>, "contentId": <string>, } ] } ], "metadata": <Metadata> } end note modSender -> modSender : // Validate delivery channel // alt delivery channel exists modSender -> modUsers ++ #moccasin : //GET /users/{recipientUserId}// modSender <-- modUsers -- : //200 OK// alt deliveryChannel == sms modSender -> modSender : //Validate user's phone number// modSender -> modSms ++ #hotPink : //POST /sms// note right of modSender { "notificationId": <uuid>, "recipientUserId": <uuid>, "body": <string> } end note modSms -> modUsers ++ #moccasin : //GET /users/{recipientUserId}// modSms <-- modUsers -- : //200 OK [user data with phone number]// modSender <-- modSms -- : //200 OK// else deliveryChannel == email modSender -> modEmail ++ #darkSeaGreen : // POST /email // note right of modSender { "notificationId": <uuid>, "recipientUserId": <uuid>, "from": <string>, "to": <string>, "header": <string>, "outputFormat": <string>, "body": <string>, "attachments": [ { "contentType": <string>, "name": <string>, "description": <string>, "data": <string>, "disposition": <string>, "contentId": <string>, } ], "additionalProperties": <map[string,object]> } end note modSender <-- modEmail -- : //200 OK// else deliveryChannel == mail modSender -> modBatchPrint ++ #darkSeaGreen : //POST /mail// note right of modSender { "notificationId": <string>, "from": <string>, "to": <string>, "outputFormat": <string>, "body": <string> } end note modSender <-- modBatchPrint -- : //200 OK// end end modNotify <-- modSender -- : //204 No Content// modCirc <-- modNotify --: //200 OK// modCirc -> kafka ++ #moccasin : //send NoticeLogEvent message// modCirc <-- kafka -- : //accepted message & acks// end [<-- modCirc -- : 201 Created @enduml

New module: mod-sms

Providers

Amazon Simple Notification Service (AWS SNS)

URL shortener is not part of default implementation in SNS API or newer AWS End User Messaging

Vonage

Required configuration values

Configuration Key

Description

Configuration Key

Description

VONAGE_API_KEY

Secure Value: API key - retrieved using dashboard

VONAGE_API_SECRET

Secure Value: API secret - retrieved using dashboard

URL shortener is not part of default implementation in SMS API

Twilio

Provides an ability to send not only SMS messages, but RCS, What’s App notification and Facebook notifications\

Provides internal ability to shorten URL links using shortenUrls request body parameter

Configuration Key

Description

Configuration Key

Description

TWILIO_ACCOUNT_SID

Secure Value: API key - retrieved using dashboard

TWILIO_AUTH_TOKEN

Secure Value: API secret - retrieved using dashboard

Module framework

Module will be implemented using Spring. Message connectors must be provided as conditional singleton beans per tenant.

Application

  • New app-sms containing UI and backend changes for SMS functionality; depends on app-notification

Audit

  • mod-audit supports auditing patron notifications

  • An additional table in mod-sms will track new and ongoing notifications with retry policies based on ENV variables, if needed

  • mod-sms tables will store PII such as patron phone numbers and notification text for retries. The Timer API endpoint should clear all unsent records based on event TTL, configured by ENV variables or per tenant via the Settings API.

Development

End-to-End testing

  • Use MockServer to stub communication with external services

  • Rancher Development and Snapshot testing rely on MockServer capabilities

  • Limited access environment can validate SMS notifications to real phone numbers

Secure Values management

Local Settings API will be provided by mod-sms service and it will provide to configure a provider and general settings related to it.

Secure value must be stored using default approach for Folio: https://github.com/folio-org/applications-poc-tools/tree/master/folio-secret-store