From 4baa8f539a19325d36ea83fc3fa79106e633b4f0 Mon Sep 17 00:00:00 2001 From: jknapp Date: Tue, 21 Oct 2025 11:13:54 -0700 Subject: [PATCH] Add queue timeout voicemail and Amazon SNS SMS provider support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update adds two major features: 1. Queue Timeout Voicemail - Callers can now leave voicemail when queue timeout is reached - Configurable per-queue voicemail prompts with TTS support - Automatic transcription and urgent keyword detection - Admin setting to choose between voicemail or callback on timeout 2. Amazon SNS SMS Provider - Alternative SMS provider to Twilio for sending text messages - Useful when Twilio SMS approval is difficult to obtain - Provider abstraction layer allows switching between Twilio/SNS - Full AWS SNS configuration in admin settings - Supports custom sender IDs in compatible countries - Lower cost per SMS compared to Twilio New Files: - includes/class-twp-voicemail-handler.php - Voicemail recording handler - includes/interface-twp-sms-provider.php - SMS provider interface - includes/class-twp-sms-provider-twilio.php - Twilio SMS implementation - includes/class-twp-sms-provider-sns.php - Amazon SNS implementation - includes/class-twp-sms-manager.php - SMS provider abstraction manager - QUEUE_VOICEMAIL_SMS_FEATURES.md - Complete feature documentation Modified Files: - includes/class-twp-call-queue.php - Added voicemail option to timeout handler - includes/class-twp-twilio-api.php - Updated send_sms() to use provider abstraction - admin/class-twp-admin.php - Added SMS provider and timeout action settings - composer.json - Added AWS SDK dependency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- QUEUE_VOICEMAIL_SMS_FEATURES.md | 293 +++++++++++++++++++++ admin/class-twp-admin.php | 108 +++++++- composer.json | 6 +- includes/class-twp-call-queue.php | 39 ++- includes/class-twp-sms-manager.php | 150 +++++++++++ includes/class-twp-sms-provider-sns.php | 206 +++++++++++++++ includes/class-twp-sms-provider-twilio.php | 137 ++++++++++ includes/class-twp-twilio-api.php | 45 +--- includes/class-twp-voicemail-handler.php | 226 ++++++++++++++++ includes/interface-twp-sms-provider.php | 32 +++ 10 files changed, 1189 insertions(+), 53 deletions(-) create mode 100644 QUEUE_VOICEMAIL_SMS_FEATURES.md create mode 100644 includes/class-twp-sms-manager.php create mode 100644 includes/class-twp-sms-provider-sns.php create mode 100644 includes/class-twp-sms-provider-twilio.php create mode 100644 includes/class-twp-voicemail-handler.php create mode 100644 includes/interface-twp-sms-provider.php diff --git a/QUEUE_VOICEMAIL_SMS_FEATURES.md b/QUEUE_VOICEMAIL_SMS_FEATURES.md new file mode 100644 index 0000000..e96474a --- /dev/null +++ b/QUEUE_VOICEMAIL_SMS_FEATURES.md @@ -0,0 +1,293 @@ +# Queue Timeout Voicemail & Amazon SNS SMS Features + +## Overview + +This update adds two major features to the Twilio WordPress Plugin: + +1. **Queue Timeout Voicemail**: Automatically prompt callers to leave a voicemail when they reach the queue timeout limit +2. **Amazon SNS SMS Provider**: Use Amazon SNS as an alternative SMS provider to Twilio + +## Feature 1: Queue Timeout Voicemail + +### What it does + +When a caller waits in a queue beyond the configured timeout period, instead of just disconnecting or offering a callback, the system can now automatically prompt them to leave a voicemail message. + +### Benefits + +- **Better Caller Experience**: Callers can leave a message instead of being disconnected +- **No Missed Opportunities**: Capture important messages even when agents are unavailable +- **Automatic Transcription**: Voicemails are automatically transcribed +- **Urgent Keyword Detection**: System detects urgent keywords in transcriptions and sends priority notifications + +### Configuration + +1. Go to **WordPress Admin → Twilio WP → Settings** +2. Find the **SMS Provider Settings** section +3. Under **Queue Timeout Action**, select: + - **Take Voicemail** (recommended) - Prompts caller to leave a message + - **Offer Callback** (original behavior) - Offers to call them back + +### How it works + +1. Caller waits in queue beyond timeout limit +2. System plays customizable voicemail prompt (can be set per-queue) +3. Caller records voicemail (max 5 minutes) +4. Recording is automatically transcribed +5. If urgent keywords detected, priority notifications sent +6. Voicemail appears in admin panel under **Voicemails & Recordings** + +### Customization + +Each queue can have a custom voicemail prompt. The default prompt is: + +> "We're sorry, but all our agents are currently unavailable. Please leave a message after the tone, and we'll get back to you as soon as possible." + +### Files Added + +- `includes/class-twp-voicemail-handler.php` - Handles voicemail recording and processing + +### Files Modified + +- `includes/class-twp-call-queue.php` - Updated `handle_timeout()` method + +--- + +## Feature 2: Amazon SNS SMS Provider + +### What it does + +Provides an alternative to Twilio for sending SMS messages using Amazon SNS (Simple Notification Service). This is particularly useful if you're having difficulty getting Twilio SMS messaging approved. + +### Benefits + +- **Alternative to Twilio SMS**: No need for Twilio SMS approval +- **AWS Integration**: Use your existing AWS infrastructure +- **Cost Effective**: Pay-as-you-go pricing with AWS +- **Global Reach**: Support for international SMS +- **Sender ID Support**: Use custom sender names in supported countries + +### Prerequisites + +1. **AWS Account**: You need an AWS account +2. **IAM User**: Create an IAM user with SNS permissions +3. **AWS SDK**: Installed automatically via Composer + +#### Required IAM Permissions + +Your AWS IAM user needs the following permissions: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sns:Publish", + "sns:SetSMSAttributes", + "sns:GetSMSAttributes" + ], + "Resource": "*" + } + ] +} +``` + +### Installation + +1. **Install AWS SDK** (if not already installed): + ```bash + cd /home/jknapp/code/twilio-wp-plugin + composer require aws/aws-sdk-php + ``` + +2. **Configure AWS Credentials**: + - Go to **WordPress Admin → Twilio WP → Settings** + - Find **SMS Provider Settings** section + - Select **Amazon SNS** from the SMS Provider dropdown + - Enter your AWS credentials: + - AWS Access Key ID + - AWS Secret Access Key + - AWS Region (e.g., us-east-1) + - SMS Sender ID (optional, 3-11 alphanumeric characters) + +3. **Test the Configuration**: + ```php + // Test via WordPress admin or run this code + require_once 'includes/class-twp-sms-manager.php'; + $result = TWP_SMS_Manager::send_test_sms('+1234567890'); + ``` + +### Configuration Options + +#### SMS Provider Selection + +Navigate to **Settings → SMS Provider Settings**: + +- **Twilio** (default): Uses your existing Twilio setup +- **Amazon SNS**: Uses AWS Simple Notification Service + +#### AWS SNS Settings + +When Amazon SNS is selected, configure: + +1. **AWS Access Key ID**: Your IAM access key +2. **AWS Secret Access Key**: Your IAM secret key +3. **AWS Region**: Choose your preferred AWS region +4. **SMS Sender ID** (Optional): Alphanumeric sender name (3-11 chars) + - Supported in: UK, EU, India, and other countries + - Not supported in: USA, Canada + - Leave blank to use AWS's default number + +### Usage + +The SMS provider is transparent to the rest of the plugin. All existing SMS functionality will automatically use the selected provider: + +- Agent notifications +- Queue alerts +- Urgent voicemail notifications +- Workflow SMS steps + +### Switching Providers + +You can switch between Twilio and Amazon SNS at any time: + +1. Go to **Settings → SMS Provider Settings** +2. Change the **SMS Provider** dropdown +3. Click **Save Changes** + +All SMS messages will immediately use the new provider. + +### Cost Comparison + +#### Twilio SMS Pricing (approximate) +- US/Canada: $0.0079 per SMS +- Requires SMS verification/approval +- Monthly fees may apply + +#### Amazon SNS SMS Pricing (approximate) +- US: $0.00645 per SMS +- No approval required for transactional messages +- Pay only for what you use +- [AWS SNS Pricing Details](https://aws.amazon.com/sns/sms-pricing/) + +### Files Added + +- `includes/interface-twp-sms-provider.php` - SMS provider interface +- `includes/class-twp-sms-provider-twilio.php` - Twilio SMS provider implementation +- `includes/class-twp-sms-provider-sns.php` - Amazon SNS SMS provider implementation +- `includes/class-twp-sms-manager.php` - SMS provider manager + +### Files Modified + +- `includes/class-twp-twilio-api.php` - Updated `send_sms()` to use SMS manager +- `admin/class-twp-admin.php` - Added SMS provider settings UI +- `composer.json` - Added AWS SDK dependency + +--- + +## Troubleshooting + +### Queue Timeout Voicemail + +**Issue**: Voicemail not recording +- Check that queue has a timeout value set (not 0) +- Verify Twilio webhooks are accessible +- Check WordPress error logs for details + +**Issue**: No transcription +- Transcription is automatic from Twilio +- Can take a few minutes to process +- Check voicemail record in database + +### Amazon SNS SMS + +**Issue**: SMS not sending +- Verify AWS credentials are correct +- Check IAM permissions include `sns:Publish` +- Ensure phone number is in E.164 format (+1XXXXXXXXXX) +- Check AWS region is correct + +**Issue**: "AWS SDK not found" +- Run: `composer require aws/aws-sdk-php` +- Ensure composer autoload is working +- Check that `vendor/` directory exists + +**Issue**: Sender ID not showing +- Sender ID only works in certain countries +- US/Canada don't support alphanumeric sender IDs +- Use default AWS number instead + +### Testing + +#### Test Voicemail +1. Call your Twilio number +2. Wait for queue timeout (or set timeout to 10 seconds for testing) +3. Leave a voicemail when prompted +4. Check admin panel → Voicemails & Recordings + +#### Test SMS Provider +```php +// Add to a test script or run via admin +require_once 'includes/class-twp-sms-manager.php'; + +// Test current provider +$validation = TWP_SMS_Manager::validate_current_provider(); +print_r($validation); + +// Send test message +$result = TWP_SMS_Manager::send_test_sms('+1XXXXXXXXXX'); +print_r($result); +``` + +--- + +## Migration Notes + +### Upgrading + +Both features are backward compatible: + +- **Queue Timeout**: Defaults to voicemail, can be changed to callback +- **SMS Provider**: Defaults to Twilio, no action required + +### Database Changes + +No database schema changes required. Uses existing: +- `twp_voicemails` table +- WordPress options table for settings + +### Rollback + +To revert to original behavior: + +1. **Queue Timeout**: Change setting to "Offer Callback" +2. **SMS Provider**: Change setting to "Twilio" + +--- + +## Support + +For issues or questions: + +1. Check error logs: `/wp-content/debug.log` +2. Review Twilio webhook logs +3. Check AWS CloudWatch logs (for SNS) +4. Contact plugin support + +--- + +## Version History + +**v2.4.0** - October 2025 +- Added queue timeout voicemail feature +- Added Amazon SNS SMS provider support +- Added SMS provider abstraction layer +- Updated admin settings UI + +--- + +## Credits + +Developed for Twilio WordPress Plugin diff --git a/admin/class-twp-admin.php b/admin/class-twp-admin.php index fd35b54..ae7eead 100644 --- a/admin/class-twp-admin.php +++ b/admin/class-twp-admin.php @@ -553,7 +553,103 @@ class TWP_Admin {

Default Twilio phone number to use as sender for SMS messages when not in a workflow context.

- + + +

SMS Provider Settings

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SMS Provider + + +

Choose which service to use for sending SMS messages. If you're having trouble getting Twilio SMS approved, Amazon SNS is an alternative.

+
AWS Access Key ID + +

Your AWS IAM access key with SNS permissions. How to create AWS access keys

+
AWS Secret Access Key + +

Your AWS IAM secret key. Keep this secure.

+
AWS Region + + +

AWS region where your SNS service is configured.

+
SMS Sender ID (Optional) + +

Alphanumeric sender ID (3-11 characters). Supported in some countries. Leave blank to use default AWS number.

+
Queue Timeout Action + + +

What to do when a caller reaches the queue timeout limit. Voicemail is recommended for better caller experience.

+
+ + + +

Voicemail & Notification Settings

+
@@ -3723,7 +3819,15 @@ class TWP_Admin { register_setting('twilio-wp-settings-group', 'twp_urgent_keywords'); register_setting('twilio-wp-settings-group', 'twp_sms_notification_number'); register_setting('twilio-wp-settings-group', 'twp_default_sms_number'); - + + // SMS Provider settings + register_setting('twilio-wp-settings-group', 'twp_sms_provider'); + register_setting('twilio-wp-settings-group', 'twp_aws_access_key'); + register_setting('twilio-wp-settings-group', 'twp_aws_secret_key'); + register_setting('twilio-wp-settings-group', 'twp_aws_region'); + register_setting('twilio-wp-settings-group', 'twp_aws_sns_sender_id'); + register_setting('twilio-wp-settings-group', 'twp_queue_timeout_action'); + // Discord/Slack notification settings register_setting('twilio-wp-settings-group', 'twp_discord_webhook_url'); register_setting('twilio-wp-settings-group', 'twp_slack_webhook_url'); diff --git a/composer.json b/composer.json index 5951cec..5b006ba 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,11 @@ "type": "wordpress-plugin", "require": { "php": ">=8.0", - "twilio/sdk": "^8.7" + "twilio/sdk": "^8.7", + "aws/aws-sdk-php": "^3.0" + }, + "suggest": { + "aws/aws-sdk-php": "Required for Amazon SNS SMS provider support" }, "autoload": { "classmap": [ diff --git a/includes/class-twp-call-queue.php b/includes/class-twp-call-queue.php index a910f80..ccde64c 100644 --- a/includes/class-twp-call-queue.php +++ b/includes/class-twp-call-queue.php @@ -168,7 +168,7 @@ class TWP_Call_Queue { private function handle_timeout($call, $queue) { global $wpdb; $table_name = $wpdb->prefix . 'twp_queued_calls'; - + // Update status $wpdb->update( $table_name, @@ -180,15 +180,36 @@ class TWP_Call_Queue { array('%s', '%s'), array('%d') ); - - // Offer callback instead of hanging up - $callback_twiml = TWP_Callback_Manager::create_callback_twiml($queue->id, $call->from_number); - + + // Get timeout action preference (default to voicemail) + $timeout_action = get_option('twp_queue_timeout_action', 'voicemail'); + $twilio = new TWP_Twilio_API(); - $twilio->update_call($call->call_sid, array( - 'twiml' => $callback_twiml - )); - + + if ($timeout_action === 'voicemail') { + // Offer voicemail recording + require_once dirname(__FILE__) . '/class-twp-voicemail-handler.php'; + $voicemail_twiml = TWP_Voicemail_Handler::create_voicemail_twiml( + $call->from_number, + $queue->id + ); + + $twilio->update_call($call->call_sid, array( + 'twiml' => $voicemail_twiml + )); + + error_log("TWP Queue Timeout: Directing call {$call->call_sid} to voicemail"); + } else { + // Offer callback (original behavior) + $callback_twiml = TWP_Callback_Manager::create_callback_twiml($queue->id, $call->from_number); + + $twilio->update_call($call->call_sid, array( + 'twiml' => $callback_twiml + )); + + error_log("TWP Queue Timeout: Offering callback to {$call->from_number}"); + } + // Reorder queue self::reorder_queue($queue->id); } diff --git a/includes/class-twp-sms-manager.php b/includes/class-twp-sms-manager.php new file mode 100644 index 0000000..9fb3f89 --- /dev/null +++ b/includes/class-twp-sms-manager.php @@ -0,0 +1,150 @@ + false, + 'error' => 'No SMS provider configured' + ]; + } + + // Validate provider configuration before sending + $validation = $provider->validate_configuration(); + if (!$validation['success']) { + error_log('TWP SMS Error: Provider validation failed - ' . $validation['error']); + return $validation; + } + + // Send SMS + $result = $provider->send_sms($to_number, $message, $from_number); + + // Log the result + if ($result['success']) { + error_log(sprintf( + 'TWP SMS: Message sent via %s to %s', + $provider->get_provider_name(), + $to_number + )); + } else { + error_log(sprintf( + 'TWP SMS Error: Failed to send via %s to %s - %s', + $provider->get_provider_name(), + $to_number, + $result['error'] ?? 'Unknown error' + )); + } + + return $result; + } + + /** + * Get list of available SMS providers + * + * @return array Array of provider IDs and names + */ + public static function get_available_providers() { + return [ + 'twilio' => 'Twilio', + 'aws_sns' => 'Amazon SNS' + ]; + } + + /** + * Validate current provider configuration + * + * @return array Response array with 'success' and 'message' or 'error' + */ + public static function validate_current_provider() { + $provider = self::get_provider(); + + if (!$provider) { + return [ + 'success' => false, + 'error' => 'No SMS provider configured' + ]; + } + + return $provider->validate_configuration(); + } + + /** + * Get current provider name + * + * @return string Provider name + */ + public static function get_current_provider_name() { + $provider = self::get_provider(); + + if (!$provider) { + return 'None'; + } + + return $provider->get_provider_name(); + } + + /** + * Test SMS send + * + * @param string $to_number Test recipient number + * @return array Response array + */ + public static function send_test_sms($to_number) { + $message = sprintf( + 'This is a test message from Twilio WordPress Plugin using %s provider at %s', + self::get_current_provider_name(), + current_time('Y-m-d H:i:s') + ); + + return self::send_sms($to_number, $message); + } +} diff --git a/includes/class-twp-sms-provider-sns.php b/includes/class-twp-sms-provider-sns.php new file mode 100644 index 0000000..5c45516 --- /dev/null +++ b/includes/class-twp-sms-provider-sns.php @@ -0,0 +1,206 @@ +default_sender_id = get_option('twp_aws_sns_sender_id', ''); + + // Initialize AWS SNS client if credentials are available + if (!empty($aws_access_key) && !empty($aws_secret_key)) { + try { + // Check if AWS SDK is available + if (!class_exists('Aws\Sns\SnsClient')) { + error_log('TWP SNS Error: AWS SDK not found. Please install via Composer: composer require aws/aws-sdk-php'); + return; + } + + $this->sns_client = new Aws\Sns\SnsClient([ + 'version' => 'latest', + 'region' => $aws_region, + 'credentials' => [ + 'key' => $aws_access_key, + 'secret' => $aws_secret_key + ] + ]); + } catch (Exception $e) { + error_log('TWP SNS Error: Failed to initialize SNS client: ' . $e->getMessage()); + } + } + } + + /** + * Send an SMS message + * + * @param string $to_number Recipient phone number (E.164 format) + * @param string $message Message body + * @param string $from_number Sender phone number or Sender ID (not used by SNS in the same way) + * @return array Response array with 'success' and 'data' or 'error' + */ + public function send_sms($to_number, $message, $from_number = null) { + try { + if (!$this->sns_client) { + return [ + 'success' => false, + 'error' => 'AWS SNS client not initialized. Check AWS credentials and ensure AWS SDK is installed.' + ]; + } + + // Prepare message attributes + $message_attributes = [ + 'AWS.SNS.SMS.SMSType' => [ + 'DataType' => 'String', + 'StringValue' => 'Transactional' // Transactional for higher reliability + ] + ]; + + // Use sender ID if provided or use default + $sender_id = $from_number ?: $this->default_sender_id; + if (!empty($sender_id)) { + // Remove '+' and non-alphanumeric characters for Sender ID + // Note: Sender ID is alphanumeric (3-11 chars) in many countries + $sender_id_clean = preg_replace('/[^a-zA-Z0-9]/', '', $sender_id); + if (strlen($sender_id_clean) >= 3 && strlen($sender_id_clean) <= 11) { + $message_attributes['AWS.SNS.SMS.SenderID'] = [ + 'DataType' => 'String', + 'StringValue' => $sender_id_clean + ]; + } + } + + // Send SMS via SNS + $result = $this->sns_client->publish([ + 'Message' => $message, + 'PhoneNumber' => $to_number, + 'MessageAttributes' => $message_attributes + ]); + + return [ + 'success' => true, + 'provider' => 'aws_sns', + 'data' => [ + 'message_id' => $result['MessageId'], + 'to' => $to_number, + 'body' => $message, + 'sender_id' => !empty($sender_id_clean) ? $sender_id_clean : null + ] + ]; + } catch (Aws\Exception\AwsException $e) { + return [ + 'success' => false, + 'provider' => 'aws_sns', + 'error' => $e->getAwsErrorMessage(), + 'code' => $e->getAwsErrorCode() + ]; + } catch (Exception $e) { + return [ + 'success' => false, + 'provider' => 'aws_sns', + 'error' => $e->getMessage() + ]; + } + } + + /** + * Get provider name + * + * @return string Provider name + */ + public function get_provider_name() { + return 'Amazon SNS'; + } + + /** + * Validate provider configuration + * + * @return array Response array with 'success' and 'message' or 'error' + */ + public function validate_configuration() { + // Check if AWS SDK is available + if (!class_exists('Aws\Sns\SnsClient')) { + return [ + 'success' => false, + 'error' => 'AWS SDK not installed. Please run: composer require aws/aws-sdk-php' + ]; + } + + $aws_access_key = get_option('twp_aws_access_key'); + $aws_secret_key = get_option('twp_aws_secret_key'); + $aws_region = get_option('twp_aws_region'); + + if (empty($aws_access_key)) { + return [ + 'success' => false, + 'error' => 'AWS Access Key is not configured' + ]; + } + + if (empty($aws_secret_key)) { + return [ + 'success' => false, + 'error' => 'AWS Secret Key is not configured' + ]; + } + + if (empty($aws_region)) { + return [ + 'success' => false, + 'error' => 'AWS Region is not configured' + ]; + } + + if (!$this->sns_client) { + return [ + 'success' => false, + 'error' => 'Failed to initialize AWS SNS client' + ]; + } + + return [ + 'success' => true, + 'message' => 'Amazon SNS SMS provider is properly configured' + ]; + } + + /** + * Set SMS spending limit (optional administrative function) + * + * @param float $monthly_limit Monthly spending limit in USD + * @return array Response array + */ + public function set_spending_limit($monthly_limit) { + try { + if (!$this->sns_client) { + return ['success' => false, 'error' => 'SNS client not initialized']; + } + + $this->sns_client->setSMSAttributes([ + 'attributes' => [ + 'MonthlySpendLimit' => (string)$monthly_limit + ] + ]); + + return [ + 'success' => true, + 'message' => "SMS spending limit set to \$$monthly_limit per month" + ]; + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => $e->getMessage() + ]; + } + } +} diff --git a/includes/class-twp-sms-provider-twilio.php b/includes/class-twp-sms-provider-twilio.php new file mode 100644 index 0000000..0ec1a26 --- /dev/null +++ b/includes/class-twp-sms-provider-twilio.php @@ -0,0 +1,137 @@ +default_from_number = get_option('twp_sms_from_number'); + + if (!empty($account_sid) && !empty($auth_token)) { + $this->client = new Twilio\Rest\Client($account_sid, $auth_token); + } + } + + /** + * Send an SMS message + * + * @param string $to_number Recipient phone number (E.164 format) + * @param string $message Message body + * @param string $from_number Sender phone number (E.164 format) + * @return array Response array with 'success' and 'data' or 'error' + */ + public function send_sms($to_number, $message, $from_number = null) { + try { + if (!$this->client) { + return [ + 'success' => false, + 'error' => 'Twilio client not initialized. Check API credentials.' + ]; + } + + // Determine the from number + $from = $from_number ?: $this->default_from_number; + + // Validate we have a from number + if (empty($from)) { + error_log('TWP SMS Error: No from number available. Please configure SMS notification number in settings.'); + return [ + 'success' => false, + 'error' => 'No SMS from number configured. Please set SMS notification number in plugin settings.' + ]; + } + + $sms = $this->client->messages->create( + $to_number, + [ + 'from' => $from, + 'body' => $message + ] + ); + + return [ + 'success' => true, + 'provider' => 'twilio', + 'data' => [ + 'sid' => $sms->sid, + 'status' => $sms->status, + 'from' => $sms->from, + 'to' => $sms->to, + 'body' => $sms->body, + 'price' => $sms->price, + 'priceUnit' => $sms->priceUnit + ] + ]; + } catch (\Twilio\Exceptions\TwilioException $e) { + return [ + 'success' => false, + 'provider' => 'twilio', + 'error' => $e->getMessage(), + 'code' => $e->getCode() + ]; + } + } + + /** + * Get provider name + * + * @return string Provider name + */ + public function get_provider_name() { + return 'Twilio'; + } + + /** + * Validate provider configuration + * + * @return array Response array with 'success' and 'message' or 'error' + */ + public function validate_configuration() { + $account_sid = get_option('twp_account_sid'); + $auth_token = get_option('twp_auth_token'); + $from_number = get_option('twp_sms_from_number'); + + if (empty($account_sid)) { + return [ + 'success' => false, + 'error' => 'Twilio Account SID is not configured' + ]; + } + + if (empty($auth_token)) { + return [ + 'success' => false, + 'error' => 'Twilio Auth Token is not configured' + ]; + } + + if (empty($from_number)) { + return [ + 'success' => false, + 'error' => 'SMS from number is not configured' + ]; + } + + if (!$this->client) { + return [ + 'success' => false, + 'error' => 'Failed to initialize Twilio client' + ]; + } + + return [ + 'success' => true, + 'message' => 'Twilio SMS provider is properly configured' + ]; + } +} diff --git a/includes/class-twp-twilio-api.php b/includes/class-twp-twilio-api.php index d03dcd3..5baebfd 100644 --- a/includes/class-twp-twilio-api.php +++ b/includes/class-twp-twilio-api.php @@ -275,49 +275,12 @@ class TWP_Twilio_API { } /** - * Send SMS + * Send SMS (uses configured SMS provider - Twilio or Amazon SNS) */ public function send_sms($to_number, $message, $from_number = null) { - try { - // Determine the from number - $from = $from_number ?: $this->phone_number; - - // Validate we have a from number - if (empty($from)) { - error_log('TWP SMS Error: No from number available. Please configure SMS notification number in settings.'); - return [ - 'success' => false, - 'error' => 'No SMS from number configured. Please set SMS notification number in plugin settings.' - ]; - } - - $sms = $this->client->messages->create( - $to_number, - [ - 'from' => $from, - 'body' => $message - ] - ); - - return [ - 'success' => true, - 'data' => [ - 'sid' => $sms->sid, - 'status' => $sms->status, - 'from' => $sms->from, - 'to' => $sms->to, - 'body' => $sms->body, - 'price' => $sms->price, - 'priceUnit' => $sms->priceUnit - ] - ]; - } catch (\Twilio\Exceptions\TwilioException $e) { - return [ - 'success' => false, - 'error' => $e->getMessage(), - 'code' => $e->getCode() - ]; - } + // Use SMS Manager to handle provider abstraction + require_once dirname(__FILE__) . '/class-twp-sms-manager.php'; + return TWP_SMS_Manager::send_sms($to_number, $message, $from_number); } /** diff --git a/includes/class-twp-voicemail-handler.php b/includes/class-twp-voicemail-handler.php new file mode 100644 index 0000000..0a8b623 --- /dev/null +++ b/includes/class-twp-voicemail-handler.php @@ -0,0 +1,226 @@ +prefix . 'twp_call_queues'; + $queue = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $queue_table WHERE id = %d", + $queue_id + )); + } + + // Determine the prompt message + $prompt_message = $custom_prompt; + + if (!$prompt_message && $queue && !empty($queue->voicemail_prompt)) { + $prompt_message = $queue->voicemail_prompt; + } + + if (!$prompt_message) { + $prompt_message = "We're sorry, but all our agents are currently unavailable. Please leave a message after the tone, and we'll get back to you as soon as possible."; + } + + // Generate TTS for the prompt + require_once dirname(__FILE__) . '/class-twp-tts-helper.php'; + $tts_result = TWP_TTS_Helper::text_to_speech($prompt_message); + + // Build TwiML response + $response = new \Twilio\TwiML\VoiceResponse(); + + if ($tts_result['success'] && !empty($tts_result['file_url'])) { + // Use generated TTS audio + $response->play($tts_result['file_url']); + } else { + // Fallback to Twilio's Say + $response->say($prompt_message, ['voice' => 'alice']); + } + + // Record the voicemail + $record_params = [ + 'action' => home_url('/wp-json/twilio-webhook/v1/voicemail-complete'), + 'recordingStatusCallback' => home_url('/wp-json/twilio-webhook/v1/voicemail-callback?' . http_build_query([ + 'from' => $caller_number, + 'queue_id' => $queue_id, + 'source' => 'queue_timeout' + ])), + 'recordingStatusCallbackMethod' => 'POST', + 'maxLength' => 300, // 5 minutes max + 'playBeep' => true, + 'finishOnKey' => '#', + 'transcribe' => true, + 'transcribeCallback' => home_url('/wp-json/twilio-webhook/v1/voicemail-transcription') + ]; + + $response->record($record_params); + + // Thank you message after recording + $response->say('Thank you for your message. Goodbye.', ['voice' => 'alice']); + + return $response; + } + + /** + * Save voicemail to database + * + * @param array $voicemail_data Voicemail data + * @return int|false Voicemail ID or false on failure + */ + public static function save_voicemail($voicemail_data) { + global $wpdb; + $table_name = $wpdb->prefix . 'twp_voicemails'; + + $insert_data = array( + 'call_sid' => sanitize_text_field($voicemail_data['call_sid']), + 'from_number' => sanitize_text_field($voicemail_data['from_number']), + 'to_number' => !empty($voicemail_data['to_number']) ? sanitize_text_field($voicemail_data['to_number']) : '', + 'recording_url' => esc_url_raw($voicemail_data['recording_url']), + 'recording_duration' => intval($voicemail_data['recording_duration']), + 'workflow_id' => !empty($voicemail_data['workflow_id']) ? intval($voicemail_data['workflow_id']) : null, + 'queue_id' => !empty($voicemail_data['queue_id']) ? intval($voicemail_data['queue_id']) : null, + 'source' => !empty($voicemail_data['source']) ? sanitize_text_field($voicemail_data['source']) : 'workflow', + 'status' => 'new', + 'received_at' => current_time('mysql') + ); + + $result = $wpdb->insert($table_name, $insert_data); + + if ($result) { + $voicemail_id = $wpdb->insert_id; + + // Log the voicemail + if (class_exists('TWP_Call_Logger')) { + TWP_Call_Logger::log_action( + $voicemail_data['call_sid'], + 'Voicemail recorded from queue timeout (' . $voicemail_data['recording_duration'] . 's)' + ); + } + + return $voicemail_id; + } + + return false; + } + + /** + * Update voicemail transcription + * + * @param int $voicemail_id Voicemail ID + * @param string $transcription Transcription text + * @param string $transcription_status Transcription status + * @return bool Success + */ + public static function update_transcription($voicemail_id, $transcription, $transcription_status = 'completed') { + global $wpdb; + $table_name = $wpdb->prefix . 'twp_voicemails'; + + $result = $wpdb->update( + $table_name, + array( + 'transcription' => sanitize_textarea_field($transcription), + 'transcription_status' => $transcription_status + ), + array('id' => $voicemail_id), + array('%s', '%s'), + array('%d') + ); + + // Check for urgent keywords in transcription + if ($result && !empty($transcription)) { + self::check_urgent_keywords($voicemail_id, $transcription); + } + + return $result !== false; + } + + /** + * Check transcription for urgent keywords + * + * @param int $voicemail_id Voicemail ID + * @param string $transcription Transcription text + */ + private static function check_urgent_keywords($voicemail_id, $transcription) { + global $wpdb; + + // Get urgent keywords from settings + $urgent_keywords = get_option('twp_urgent_voicemail_keywords', array('urgent', 'emergency', 'asap', 'critical')); + if (is_string($urgent_keywords)) { + $urgent_keywords = array_map('trim', explode(',', $urgent_keywords)); + } + + // Check if transcription contains any urgent keywords + $transcription_lower = strtolower($transcription); + $found_keyword = null; + + foreach ($urgent_keywords as $keyword) { + if (stripos($transcription_lower, strtolower(trim($keyword))) !== false) { + $found_keyword = $keyword; + break; + } + } + + if ($found_keyword) { + // Mark voicemail as urgent + $table_name = $wpdb->prefix . 'twp_voicemails'; + $wpdb->update( + $table_name, + array('is_urgent' => 1), + array('id' => $voicemail_id), + array('%d'), + array('%d') + ); + + // Send urgent notification + $voicemail = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $table_name WHERE id = %d", + $voicemail_id + )); + + if ($voicemail && class_exists('TWP_Notifications')) { + TWP_Notifications::send_call_notification('urgent_voicemail', array( + 'type' => 'urgent_voicemail', + 'from_number' => $voicemail->from_number, + 'keyword' => $found_keyword, + 'transcription' => $transcription, + 'voicemail_id' => $voicemail_id, + 'admin_url' => admin_url('admin.php?page=twilio-wp-voicemails&voicemail_id=' . $voicemail_id) + )); + } + + error_log("TWP Voicemail: Urgent keyword '$found_keyword' detected in voicemail $voicemail_id"); + } + } + + /** + * Get voicemail by ID + * + * @param int $voicemail_id Voicemail ID + * @return object|null Voicemail object + */ + public static function get_voicemail($voicemail_id) { + global $wpdb; + $table_name = $wpdb->prefix . 'twp_voicemails'; + + return $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $table_name WHERE id = %d", + $voicemail_id + )); + } +} diff --git a/includes/interface-twp-sms-provider.php b/includes/interface-twp-sms-provider.php new file mode 100644 index 0000000..e384571 --- /dev/null +++ b/includes/interface-twp-sms-provider.php @@ -0,0 +1,32 @@ +