interface/modules/custom_modules/oe-module-faxsms/WARP.md
This file provides guidance to WARP (warp.dev) when working with the OpenEMR FaxSMS Module.
oe-module-faxsms is a custom OpenEMR module that provides integrated fax and SMS messaging capabilities. The module supports multiple vendor services:
Version: 5.0.0 License: GPL-3.0 Authors: Jerry Padgett, Stephen Nielson
This module uses an abstract dispatch pattern to arbitrate and route API calls to different vendor services. The architecture supports:
oe-module-faxsms/
├── src/ # PSR-4 autoloaded classes (OpenEMR\Modules\FaxSMS\*)
│ ├── Controller/ # Service controllers and dispatch logic
│ │ ├── AppDispatch.php # Abstract base class for all service clients
│ │ ├── TwilioSMSClient.php # Twilio SMS implementation
│ │ ├── RCFaxClient.php # RingCentral Fax implementation
│ │ ├── EtherFaxActions.php # etherFAX implementation
│ │ ├── SignalWireClient.php # SignalWire Fax implementation
│ │ ├── EmailClient.php # Email notification client
│ │ ├── NotificationTaskManager.php # Appointment reminder task manager
│ │ └── AppDispatch.php # Service dispatcher
│ ├── EtherFax/ # etherFAX SDK components
│ │ ├── EtherFaxClient.php # Main client
│ │ ├── FaxAccount.php
│ │ ├── FaxReceive.php
│ │ ├── FaxResult.php
│ │ ├── FaxState.php
│ │ └── FaxStatus.php
│ ├── Events/ # Event listeners
│ │ └── NotificationEventListener.php
│ └── BootstrapService.php # Module initialization and globals management
├── library/ # Legacy utility scripts
│ ├── rc_sms_notification.php # SMS notification dispatcher
│ ├── setup_services.php # Service configuration UI
│ ├── utility.php # Utility functions
│ ├── run_notifications.php # Notification runner
│ └── api_onetime.php # One-time API setup
├── sql/ # SQL migration scripts
├── vendor/ # Composer dependencies (RingCentral SDK)
├── openemr.bootstrap.php # Module bootstrap - event listeners and menu
├── ModuleManagerListener.php # Laminas module lifecycle hooks
├── BootstrapService.php # Service initialization
├── messageUI.php # Main UI for viewing/sending messages
├── contact.php # Contact selection dialog
├── setup.php # Credential setup UI
├── setup_rc.php # RingCentral specific setup
├── setup_email.php # Email setup
├── moduleConfig.php # Module configuration iframe
├── table.sql # Database schema
├── composer.json # Module dependencies
└── version.php # Version information
Location: src/Controller/AppDispatch.php
Purpose: Base class for all service client implementations. Handles:
Abstract Methods (must be implemented by child classes):
abstract function authenticate(): string|int|bool;
abstract function sendFax(): string|bool;
abstract function sendSMS(): mixed;
abstract function sendEmail(): mixed;
abstract function fetchReminderCount(): string|bool;
Key Features:
_ACTION_COMMAND, type)$_SESSION['oefax_current_module_type'])Each vendor has its own controller class extending AppDispatch:
Bootstrap File: openemr.bootstrap.php
The module integrates with OpenEMR's Symfony event dispatcher to:
Event Listeners:
// Menu
MenuEvent::MENU_UPDATE
// Patient Reports
PatientReportEvent::ACTIONS_RENDER_POST
PatientReportEvent::JAVASCRIPT_READY_POST
// Documents
PatientDocumentEvent::ACTIONS_RENDER_FAX_ANCHOR
PatientDocumentEvent::JAVASCRIPT_READY_FAX_DIALOG
// SMS
SendSmsEvent::ACTIONS_RENDER_SMS_POST
SendSmsEvent::JAVASCRIPT_READY_SMS_POST
File: ModuleManagerListener.php
Handles module lifecycle events through Laminas Module Manager:
module_faxsms_credentialsStores encrypted vendor API credentials per user.
CREATE TABLE module_faxsms_credentials (
id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
auth_user INT(11) UNSIGNED DEFAULT 0, -- 0 for global, user ID for per-user
vendor VARCHAR(63), -- 'twilio', 'etherfax', 'ringcentral', '_persisted'
credentials MEDIUMBLOB NOT NULL, -- Encrypted JSON credentials
updated DATETIME DEFAULT CURRENT_TIMESTAMP,
setup_persist TINYTEXT,
UNIQUE KEY (auth_user, vendor)
);
Special Vendor: _persisted - Used to backup setup globals when module is disabled
oe_faxsms_queueFax/SMS message queue and history.
CREATE TABLE oe_faxsms_queue (
id INT(11) AUTO_INCREMENT PRIMARY KEY,
account TINYTEXT, -- Vendor account identifier
uid INT(11), -- OpenEMR user ID
job_id TEXT, -- Vendor job/message GUID
date DATETIME DEFAULT CURRENT_TIMESTAMP,
receive_date DATETIME,
deleted INT(1) DEFAULT 0,
calling_number TINYTEXT, -- From number
called_number TINYTEXT, -- To number
mime TINYTEXT, -- Content type
details_json LONGTEXT, -- JSON details from vendor
KEY (uid, receive_date)
);
The module uses custom globals stored in the globals table:
oefax_enable_fax - Fax vendor selection ('0'=disabled, '1'=RingCentral, '3'=etherFAX, '6'=SignalWire)oefax_enable_sms - SMS vendor selection ('0'=disabled, '1'=RingCentral, '2'=Twilio)oesms_send - Enable SMS send buttons in UIoerestrict_users - Restrict to individual user accountsoe_enable_email - Enable email remindersauth_user = 0): Credentials shared across all usersauth_user = user_id): Each user maintains their own vendor accountsSet via "Individual User Accounts" checkbox in module config.
Critical: All vendor credentials are encrypted using OpenEMR\Common\Crypto\CryptoGen before storage.
$crypto = new CryptoGen();
$encrypted = $crypto->encryptStandard(json_encode($credentials));
Never store API keys, passwords, or tokens in plain text.
Minimum ACL for module features:
['patients', 'docs']['admin', 'docs']['admin', 'demo']Verify ACL in controllers:
$clientApp->verifyAcl(); // Checks ACL before allowing access
To add a new SMS or Fax vendor:
src/Controller/ extending AppDispatch:namespace OpenEMR\Modules\FaxSMS\Controller;
class NewVendorClient extends AppDispatch
{
public function authenticate(): bool { /* ... */ }
public function sendFax(): string|bool { /* ... */ }
public function sendSMS(): mixed { /* ... */ }
public function sendEmail(): mixed { /* ... */ }
public function fetchReminderCount(): string|bool { /* ... */ }
}
Update Service Type Constants in AppDispatch.php or config
Add Setup UI in setup.php or create new setup file
Update Bootstrap Menu in openemr.bootstrap.php to include new vendor
Register in Dispatcher - Ensure AppDispatch::getApiService() can instantiate your class
ringcentral/ringcentral-php 3.0.3 (via Composer)src/EtherFax/signalwire-community/signalwire 3.2.0 (via Composer)File: messageUI.php
Features:
JavaScript Libraries:
File: contact.php
Modal dialog for selecting recipient and sending messages:
File: library/rc_sms_notification.php
Runs appointment reminder notifications via:
Configuration:
***NAME***, ***PROVIDER***, ***DATE***, ***STARTTIME***, ***ENDTIME***, ***ORG***To integrate with OpenEMR's background services:
INSERT INTO background_services (name, title, active, running, next_run,
execute_interval, function, require_once, sort_order)
VALUES ('FaxSMS_Notifications', 'FaxSMS Appointment Notifications', 1, 0, NOW(),
3600, 'send_faxsms_notifications',
'/interface/modules/custom_modules/oe-module-faxsms/library/run_notifications.php',
100);
Cause: Global variables not set or module not enabled
Solution:
SELECT * FROM globals WHERE gl_name LIKE 'oefax%'Cause: Encryption failure or database permissions
Solution:
sites/<site>/documents directory is writable (CryptoGen needs keys)module_faxsms_credentials table existsCause: User lacks required ACL permissions
Solution:
['patients', 'docs'] ACL at minimum['admin', 'docs'] ACLCause: Background services not configured or credentials invalid
Solution:
background_services table for service entrysites/<site>/documents/logs_and_misc/OpenEMR\Modules\FaxSMS\* namespace for src/ classesUse modern QueryUtils instead of legacy functions:
use OpenEMR\Common\Database\QueryUtils;
// Instead of sqlQuery()
$row = QueryUtils::querySingleRow($sql, $binds);
// Instead of sqlStatement() + loop
$rows = QueryUtils::fetchRecords($sql, $binds);
Use demo/sandbox modes when testing:
Never test with production credentials during development.
The module uses $sessionAllowWrite = true in entry points to ensure session writes:
$sessionAllowWrite = true;
require_once(__DIR__ . "/../../../globals.php");
This is required for CLI scripts and background services.
{
"require": {
"ringcentral/ringcentral-php": "3.0.3"
}
}
Important: Each module has its own composer.json. Do NOT run composer dump-autoload in the OpenEMR root directory.
Module auto-creates "FAX" category in categories table on installation:
INSERT INTO categories (name, value, parent, aco_spec)
VALUES ('FAX', '', 1, 'patients|docs');
Received faxes are stored as patient documents under this category.
sites/<site>/documents/<patient>/The module uses internal routing via _ACTION_COMMAND parameter:
Format: ?type=<service>&_ACTION_COMMAND=<action>
Examples:
?type=sms&_ACTION_COMMAND=sendSMS - Send SMS?type=fax&_ACTION_COMMAND=sendFax - Send fax?type=fax&_ACTION_COMMAND=retrieveFax - Get fax from queue?type=sms&_ACTION_COMMAND=fetchReminderCount - Get reminder countRoutes are handled by AppDispatch::dispatchActions() and mapped to controller methods.
Set in page header for JavaScript access:
let pid = <patient_id>;
let portalUrl = "<portal_url>";
let currentService = "<service_type>"; // '1'=RC, '2'=Twilio, '3'=etherFAX, '6'=SignalWire
let serviceType = "<type>"; // 'sms', 'fax', 'email'
Uses OpenEMR's dlgopen() for modal dialogs:
dlgopen(url, dialogName, modalSize, height, allowResize, title, options);
Modal Sizes: 'modal-sm', 'modal-md', 'modal-lg', 'modal-xl'
mysql -u local_openemr -p 5qy3xkMjP4A2US1u7Qv -e "
SELECT mod_name, mod_directory, enabled, mod_ui_active
FROM modules
WHERE mod_directory = 'oe-module-faxsms';"
mysql -u local_openemr -p 5qy3xkMjP4A2US1u7Qv -e "
SELECT gl_name, gl_value
FROM globals
WHERE gl_name LIKE 'oefax%' OR gl_name LIKE 'oesms%' OR gl_name = 'oe_enable_email';"
mysql -u local_openemr -p 5qy3xkMjP4A2US1u7Qv -e "
SELECT id, auth_user, vendor, updated
FROM module_faxsms_credentials;"
mysql -u local_openemr -p 5qy3xkMjP4A2US1u7Qv -e "
SELECT id, account, job_id, date, calling_number, called_number
FROM oe_faxsms_queue
ORDER BY date DESC LIMIT 20;"
/Documentation/MODULES.md (in OpenEMR root)/src/Events/ (in OpenEMR root)$sessionAllowWrite = true where neededSignalWire provides a Twilio-compatible REST API for faxing, making it an excellent alternative or addition to existing fax providers. The integration uses the signalwire-community/signalwire PHP SDK.
Credentials Needed:
example.signalwire.com)Sign up for SignalWire:
Generate API Token:
Provision Fax Number:
Configure in OpenEMR:
Configure Webhook (for receiving faxes):
https://your-openemr.com/interface/modules/custom_modules/oe-module-faxsms/library/webhook_receiver.php?type=fax&vendor=signalwireSending Faxes:
Receiving Faxes:
The webhook endpoint uses:
$ignoreAuth = true for external accessIssue: Faxes not sending
sites/<site>/documents/logs_and_misc/Issue: Webhooks not working
?type=fax&vendor=signalwire)Issue: Media download fails
SignalWire applies standard rate limits:
When working on this module:
Last Updated: December 2025 For: OpenEMR 7.0.3 / oe-module-faxsms v5.0.0