-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Template-Based Implementation Plan
Overview
This document outlines the implementation plan based on Sinch-approved templates for the Conversations API. OpenCoreEMR has a BAA (Business Associate Agreement) with Sinch and has received compliance approval for 12 message templates.
Important: These templates are approved for OpenCoreEMR's use with Sinch. Any other organization using this open-source module must:
- Establish their own relationship with Sinch
- Execute their own BAA
- Submit templates for compliance review
- Obtain approval before sending patient messages
Approved Templates Summary
Template Categories
| Category | Template | Type | Use Case |
|---|---|---|---|
| Consent | Initial Opt-In Confirmation | Individual | Required after patient opts in |
| Appointments | Appointment Reminder (High-Compliance) | Individual | Upcoming appointment notification |
| Appointments | Missed Appointment / No-Show Follow-Up | Individual | After patient misses appointment |
| Appointments | Telehealth Appointment Link | Individual | Before telehealth visit |
| Appointments | Pre-Visit Instructions | Individual | Before scheduled visit |
| Portal | Check Your Portal (New Message) | Individual | New secure message available |
| Portal | Check Your Portal (Test Results) | Individual | Test results available |
| Billing | Billing Reminder (Statement Ready) | Individual/Batch | Monthly statement cycle |
| Billing | Billing Reminder (Balance Due) | Individual/Batch | Outstanding balance |
| Wellness | Preventive Care / Wellness Reminder | Individual/Batch | Annual wellness/screening due |
| Operations | Office Closure / Emergency Update | Batch | Clinic closure notification |
| Wellness | Public Health Announcement | Batch | Health campaigns (flu shots, etc.) |
| Feedback | Post-Visit Feedback Survey | Individual | After visit completion |
Individual vs Batch Communication
Strictly Individual (1-to-1)
These require patient-specific context and should only be sent individually:
- Initial Opt-In Confirmation - Sent immediately after opt-in
- Appointment Reminder - Patient has specific appointment
- Missed Appointment Follow-Up - Patient missed specific appointment
- Telehealth Link - Patient has specific telehealth appointment
- Pre-Visit Instructions - Patient has upcoming visit
- Check Your Portal (New Message) - Patient has new message
- Check Your Portal (Test Results) - Patient has new results
- Post-Visit Feedback Survey - Patient just completed visit
Strictly Batch (1-to-many)
These are generic announcements sent to groups:
- Office Closure / Emergency Update - Broadcast to all active patients
- Public Health Announcement - Campaign to eligible cohort (e.g., all patients, patients 65+)
Flexible (Both Individual and Batch)
These can be sent in either context:
- Billing Reminder (Statement Ready) - Batch for monthly statement run, Individual for specific patient
- Billing Reminder (Balance Due) - Batch for collections cycle, Individual for specific account
- Preventive Care / Wellness Reminder - Batch for wellness campaign, Individual when patient is due
Template Variables
Standard Variables (All Templates)
// Required in all messages
{{ clinic_name }} // Organization name
{{ opt_out }} // "Reply STOP to opt-out" (REQUIRED)
{{ phone }} // Clinic phone numberTemplate-Specific Variables
// Appointment templates
{{ appt_time }} // "Saturday, Nov 15, 2025 at 2:30 PM EST"
{{ provider_name }} // Provider name (optional, PHI risk)
{{ appt_date }} // Appointment date (optional, PHI risk)
{{ portal_link }} // Secure portal URL
{{ telehealth_link }} // Telehealth video call URL
// Office closure
{{ reason_for_closure }} // "severe weather"
{{ date_closed }} // "Saturday, Nov 22, 2025"
{{ website_link }} // Clinic website URL
// Portal notifications
{{ portal_link }} // Secure portal login URL
// Billing
{{ billing_phone }} // Billing department phone
{{ portal_link }} // Portal for payment
// Wellness/preventive
{{ scheduling_link }} // Online scheduling URL
// Survey
{{ survey_link }} // Survey URL (must be HIPAA-compliant)Features to Implement
Phase 1: Core Messaging Infrastructure
1.1 Template Management System
Database Schema:
CREATE TABLE oce_sinch_message_templates (
id INT PRIMARY KEY AUTO_INCREMENT,
template_key VARCHAR(100) UNIQUE NOT NULL, -- 'appointment_reminder', 'opt_in_confirmation', etc.
template_name VARCHAR(255) NOT NULL,
category VARCHAR(50) NOT NULL, -- 'consent', 'appointments', 'portal', 'billing', etc.
communication_type ENUM('individual', 'batch', 'both') NOT NULL,
body TEXT NOT NULL, -- Template with {{ variables }}
required_variables JSON NOT NULL, -- ['clinic_name', 'portal_link', 'phone', 'opt_out']
compliance_confidence INT NOT NULL, -- 90-100%
sinch_approved BOOLEAN DEFAULT TRUE,
active BOOLEAN DEFAULT TRUE,
created_at DATETIME,
updated_at DATETIME,
INDEX idx_category (category),
INDEX idx_type (communication_type),
INDEX idx_active (active)
);Template Service:
class TemplateService
{
/**
* Render a template with variables
*/
public function render(string $templateKey, array $variables): string;
/**
* Validate required variables are present
*/
public function validateVariables(string $templateKey, array $variables): bool;
/**
* Get template by key
*/
public function getTemplate(string $templateKey): array;
/**
* List templates by category
*/
public function getTemplatesByCategory(string $category): array;
/**
* Check if template is approved for batch sending
*/
public function isBatchApproved(string $templateKey): bool;
}1.2 Consent Management
Database Schema:
CREATE TABLE oce_sinch_patient_consent (
id INT PRIMARY KEY AUTO_INCREMENT,
patient_id BIGINT NOT NULL,
phone_number VARCHAR(20) NOT NULL, -- E.164 format
opted_in BOOLEAN DEFAULT FALSE,
opt_in_method VARCHAR(50), -- 'web_form', 'in_person', 'portal'
opt_in_date DATETIME,
opt_in_ip_address VARCHAR(45),
opted_out BOOLEAN DEFAULT FALSE,
opt_out_date DATETIME,
opt_out_method VARCHAR(50), -- 'sms_stop', 'web_form', 'in_person'
consent_text TEXT, -- What they agreed to
created_at DATETIME,
updated_at DATETIME,
INDEX idx_patient (patient_id),
INDEX idx_phone (phone_number),
INDEX idx_opted_in (opted_in),
UNIQUE KEY unique_patient_phone (patient_id, phone_number)
);Consent Service:
class ConsentService
{
/**
* Check if patient has active consent
*/
public function hasConsent(int $patientId, string $phoneNumber): bool;
/**
* Record opt-in and send confirmation message
*/
public function optIn(
int $patientId,
string $phoneNumber,
string $method,
?string $ipAddress = null
): void;
/**
* Record opt-out (STOP keyword)
*/
public function optOut(
int $patientId,
string $phoneNumber,
string $method
): void;
/**
* Send initial opt-in confirmation message
*/
private function sendOptInConfirmation(int $patientId, string $phoneNumber): void;
}1.3 Keyword Handler (HELP, STOP)
Database Schema:
CREATE TABLE oce_sinch_keyword_responses (
id INT PRIMARY KEY AUTO_INCREMENT,
keyword VARCHAR(20) NOT NULL, -- 'STOP', 'START', 'HELP', 'UNSTOP'
response_template TEXT NOT NULL,
active BOOLEAN DEFAULT TRUE,
created_at DATETIME,
updated_at DATETIME,
INDEX idx_keyword (keyword)
);
-- Default responses
INSERT INTO oce_sinch_keyword_responses (keyword, response_template, active) VALUES
('STOP', '{{ clinic_name }}: You have been unsubscribed from our text notifications. You will not receive further messages. Reply START to re-subscribe or call {{ phone }} for assistance.', TRUE),
('START', '{{ clinic_name }}: You have been re-subscribed to text notifications. Reply HELP for help. Reply STOP to unsubscribe.', TRUE),
('UNSTOP', '{{ clinic_name }}: You have been re-subscribed to text notifications. Reply HELP for help. Reply STOP to unsubscribe.', TRUE),
('HELP', '{{ clinic_name }}: Text notifications from {{ clinic_name }}. For assistance, call {{ phone }}. Msg&Data rates may apply. Reply STOP to unsubscribe.', TRUE);Keyword Handler Service:
class KeywordHandlerService
{
/**
* Process inbound message for keywords
*/
public function handleInboundMessage(string $fromNumber, string $messageBody): ?string;
/**
* Check if message contains a keyword
*/
private function detectKeyword(string $messageBody): ?string;
/**
* Handle STOP keyword
*/
private function handleStop(string $phoneNumber): string;
/**
* Handle START/UNSTOP keyword
*/
private function handleStart(string $phoneNumber): string;
/**
* Handle HELP keyword
*/
private function handleHelp(): string;
}Implementation:
public function handleInboundMessage(string $fromNumber, string $messageBody): ?string
{
$keyword = $this->detectKeyword($messageBody);
if (!$keyword) {
return null; // Not a keyword, route to provider
}
// Find patient by phone number
$patient = $this->findPatientByPhone($fromNumber);
switch (strtoupper($keyword)) {
case 'STOP':
case 'STOPALL':
case 'UNSUBSCRIBE':
case 'CANCEL':
case 'END':
case 'QUIT':
return $this->handleStop($fromNumber, $patient);
case 'START':
case 'UNSTOP':
case 'SUBSCRIBE':
return $this->handleStart($fromNumber, $patient);
case 'HELP':
case 'INFO':
return $this->handleHelp();
default:
return null;
}
}
private function handleStop(string $phoneNumber, ?array $patient): string
{
if ($patient) {
$this->consentService->optOut(
$patient['pid'],
$phoneNumber,
'sms_stop'
);
}
return $this->templateService->render('keyword_stop', [
'clinic_name' => $this->config->getClinicName(),
'phone' => $this->config->getClinicPhone()
]);
}Phase 2: Appointment Features
2.1 Appointment Reminders (Individual)
Features:
- Send high-compliance reminder (no appointment details in SMS)
- Link to patient portal for full details
- Configurable reminder timing (24h, 2h before, etc.)
- Track delivery status
Service Implementation:
class AppointmentReminderService
{
/**
* Send appointment reminder
*/
public function sendReminder(int $appointmentId, string $timing = '24h'): void
{
$appointment = $this->getAppointment($appointmentId);
$patient = $this->getPatient($appointment['pc_pid']);
// Check consent
if (!$this->consentService->hasConsent($patient['pid'], $patient['phone_cell'])) {
throw new SinchValidationException("Patient has not opted in to SMS");
}
// Render template
$message = $this->templateService->render('appointment_reminder', [
'clinic_name' => $this->config->getClinicName(),
'portal_link' => $this->generatePortalLink($patient['pid']),
'phone' => $this->config->getClinicPhone(),
'opt_out' => 'Reply STOP to opt-out'
]);
// Send via Conversations API
$this->messageService->sendIndividual(
$patient['pid'],
$patient['phone_cell'],
$message,
['appointment_id' => $appointmentId]
);
}
}2.2 Telehealth Links (Individual)
Features:
- Send secure telehealth link before appointment
- Include appointment time in message
- Send 15-30 minutes before scheduled time
Template:
{{ clinic_name }}: Your telehealth visit is starting soon (at {{ appt_time }}).
Please log in to your secure video call here: {{ telehealth_link }}.
{{ opt_out }}.
2.3 Missed Appointment Follow-Up (Individual)
Features:
- Automatically send after no-show
- Encourage rescheduling
- Link to scheduling portal
Trigger: When appointment status changes to "no-show" or patient doesn't check in
2.4 Pre-Visit Instructions (Individual)
Features:
- Send 24-48h before appointment
- Link to portal for pre-check-in forms
- Include any special instructions
Phase 3: Portal Notifications
3.1 New Message Notification (Individual)
Trigger: When provider sends secure message to patient via portal
Template:
{{ clinic_name }}: You have a new secure message from your provider.
Please log in to your patient portal to view it: {{ portal_link }}.
Call {{ phone }} if you have trouble.
{{ opt_out }}.
3.2 Test Results Available (Individual)
Trigger: When test results are finalized and made available in portal
Template:
{{ clinic_name }}: Your recent test results are now available to view in your
secure patient portal. Please log in at: {{ portal_link }}.
Call {{ phone }} with any questions.
{{ opt_out }}.
CRITICAL: Never include actual results in SMS. Only notify that results are available.
Phase 4: Billing Features
4.1 Statement Ready (Individual/Batch)
Individual: When specific patient statement is generated
Batch: Monthly statement run for all patients with balances
Template:
{{ clinic_name }}: Your latest account statement is now available.
To view your statement and payment options, please log in to your secure portal:
{{ portal_link }}.
{{ opt_out }}.
4.2 Balance Due Reminder (Individual/Batch)
Individual: Specific patient with overdue balance
Batch: Collections cycle targeting patients with balances
Template:
{{ clinic_name }}: This is a reminder from {{ clinic_name }} that you have a
balance on your account. To view details or make a secure payment, please visit:
{{ portal_link }} or call {{ billing_phone }}.
{{ opt_out }}.
Note: Template does NOT include amount (which would be PHI)
Phase 5: Wellness & Prevention
5.1 Preventive Care Reminder (Individual/Batch)
Individual: Patient is due for annual wellness visit
Batch: Campaign for all patients due for wellness visits
Template:
{{ clinic_name }}: It's time to schedule your annual wellness visit!
Staying up-to-date on preventive care is key to your health.
Call {{ phone }} or book online: {{ scheduling_link }}.
{{ opt_out }}.
5.2 Public Health Announcements (Batch)
Use Cases:
- Flu shot campaigns
- COVID vaccine availability
- Health screenings
- Wellness programs
Template:
{{ clinic_name }}: Flu season is here! We now have flu shots available.
Protect yourself and your community. Schedule your shot today by calling
{{ phone }} or online: {{ scheduling_link }}.
{{ opt_out }}.
Phase 6: Operational Messages
6.1 Office Closure Alerts (Batch)
Triggers:
- Emergency closures (weather, power outage)
- Planned closures (holidays)
- Service disruptions
Template:
{{ clinic_name }} Alert: Due to {{ reason_for_closure }}, our office will be
closed on {{ date_closed }}. We will contact you to reschedule any appointments.
See details: {{ website_link }}.
{{ opt_out }}.
Target Audience: All active patients, or subset with appointments on closure date
6.2 Post-Visit Survey (Individual)
Trigger: After visit completion (same day or next day)
Template:
{{ clinic_name }}: Thank you for visiting {{ clinic_name }} today. We value your
feedback. Would you take 60 seconds to complete a brief survey about your
experience? {{ survey_link }}.
{{ opt_out }}.
Implementation Priority
Phase 1: Foundation (Weeks 1-2)
- ✅ Template management system
- ✅ Consent tracking database
- ✅ Initial opt-in confirmation message
- ✅ HELP/STOP keyword handler
- ✅ Basic message sending (individual)
Phase 2: Appointments (Weeks 3-4)
- ✅ Appointment reminders (high-compliance version)
- ✅ Missed appointment follow-up
- ✅ Telehealth links
- ✅ Pre-visit instructions
Phase 3: Portal Notifications (Week 5)
- ✅ New message notifications
- ✅ Test results available notifications
Phase 4: Batch Communications (Week 6)
- ✅ Batch sending infrastructure
- ✅ Office closure alerts
- ✅ Public health announcements
- ✅ Wellness campaigns
Phase 5: Billing & Surveys (Week 7)
- ✅ Statement ready notifications
- ✅ Balance due reminders
- ✅ Post-visit surveys
Phase 6: Advanced Features (Week 8+)
- ✅ Scheduled message sending
- ✅ Analytics dashboard
- ✅ A/B testing for templates
- ✅ Delivery optimization
Message Sending Guidelines
Individual Messages (1-to-1)
// Example: Send appointment reminder
$messageService->sendIndividual(
patientId: $patientId,
phoneNumber: $phoneNumber,
templateKey: 'appointment_reminder',
variables: [
'clinic_name' => 'Example Clinic',
'portal_link' => 'https://example.com/portal',
'phone' => '650-123-4567',
'opt_out' => 'Reply STOP to opt-out'
],
metadata: ['appointment_id' => 123]
);Batch Messages (1-to-many)
// Example: Send office closure alert
$patientIds = $this->getActivePatients();
$messageService->sendBatch(
patientIds: $patientIds,
templateKey: 'office_closure',
variables: [
'clinic_name' => 'Example Clinic',
'reason_for_closure' => 'severe weather',
'date_closed' => 'Saturday, Nov 22, 2025',
'website_link' => 'https://example.com/closure',
'opt_out' => 'Reply STOP to opt-out'
],
metadata: ['campaign' => 'weather_closure_2025_11_22']
);Compliance Checklist
Before sending any messages:
- Consent verified - Patient has opted in
- Template approved - Using Sinch-approved template
- Variables validated - All required variables present
- Opt-out included - Every message has {{ opt_out }}
- No PHI in SMS - Only portal links, no sensitive details
- BAA in place - OpenCoreEMR has BAA with Sinch
- Proper timing - Messages sent during reasonable hours
- Rate limits respected - Not exceeding carrier limits
Legal Disclaimer for README
## Legal Requirements & Disclaimers
### Business Associate Agreement (BAA) Required
**IMPORTANT:** This module uses the Sinch Conversations API to send text messages
to patients. **You MUST have a Business Associate Agreement (BAA) with Sinch**
before using this module in production with patient data.
OpenCoreEMR has a BAA with Sinch and has received compliance approval for the
message templates included in this module. **If you are not using this module
through OpenCoreEMR**, you must:
1. **Execute your own BAA with Sinch** before sending any patient messages
2. **Submit your message templates** to Sinch for compliance review
3. **Obtain approval** before using templates in production
4. **Maintain proper consent records** for all patients
### TCPA & HIPAA Compliance
Before sending any SMS messages, you **MUST**:
1. **Obtain Prior Express Written Consent** from patients to receive text messages
2. **Document consent** including:
- Patient signature or electronic agreement
- Date of consent
- Phone number provided
- Types of messages they agree to receive
3. **Honor opt-out requests immediately** (STOP keywords)
4. **Never include PHI in SMS** - Use portal links instead
5. **Identify yourself** in every message (clinic name)
6. **Include opt-out instructions** in every message
### Standard SMS Disclaimers
The following disclaimers are REQUIRED by carriers (CTIA) and TCPA:
**Initial Opt-In Confirmation:**{{ clinic_name }}: You have successfully opted-in to receive alerts from
{{ clinic_name }}. Msg&Data rates may apply. Msg freq varies. Reply HELP for help.
Reply STOP to unsubscribe at any time.
**This message MUST be sent immediately after a patient opts in.**
### Message Template Approval
All message templates included in this module have been reviewed and approved
by Sinch for use by OpenCoreEMR under our BAA. These templates are:
- HIPAA-compliant (when used correctly with proper consent)
- TCPA-compliant
- Carrier-compliant (CTIA guidelines)
**If you create custom templates**, you MUST submit them to Sinch for compliance
review before using them in production.
### No Warranty / Use at Your Own Risk
This open-source software is provided "as is" without warranty of any kind.
You are solely responsible for ensuring your use of this module complies with:
- HIPAA Privacy and Security Rules
- TCPA (Telephone Consumer Protection Act)
- State-specific privacy laws
- Carrier regulations (CTIA)
**Consult with legal counsel** before deploying this module in a production
healthcare environment.
### Support
For compliance questions or to establish a BAA with Sinch:
- **OpenCoreEMR Customers:** Contact [email protected]
- **Other Users:** Contact Sinch directly at https://www.sinch.com/contact/
For technical issues with this module:
- GitHub Issues: https://github.com/opencoreemr/oce-module-sinch-conversations/issues
Summary
This implementation plan provides:
- ✅ 12 Sinch-approved templates with clear categorization
- ✅ Individual vs Batch communication types defined
- ✅ HELP/STOP keyword handling for compliance
- ✅ Consent management system
- ✅ Template variable system for personalization
- ✅ Phased implementation over 8 weeks
- ✅ Legal disclaimers for README
- ✅ Compliance checklist for every message
Next Steps:
- Implement template management database and service
- Build consent tracking system
- Implement keyword handler for HELP/STOP
- Create message sending service (individual + batch)
- Integrate with OpenEMR appointment system
- Update README with legal disclaimers