Perl

Send SMS or MMS with Perl

Learn how to send your first SMS using the CCAI Perl SDK

Prerequisites

To get the most out of this guide, you'll need to:

  • Sign up for a CCAI Trial Account here
  • Get your Client ID from Account\Settings
  • Create\Copy an API Key from Account Settings

1. Install

Get the CCAI Perl SDK

git clone https://github.com/cloudcontactai/ccai-perl.git
cd ccai-perl
cpanm --installdeps .

# Set up your credentials
cp .env.example .env
# Edit .env and add your CCAI credentials

# Verify SSL configuration
perl verify_ssl.pl

# Test with examples
perl -Ilib examples/sms_send.pl

Install the required dependencies:

# Using cpanm (recommended) - installs all dependencies including SSL support
cpanm --installdeps .

# Or install individual modules
cpanm LWP::UserAgent JSON HTTP::Request::Common File::Basename MIME::Base64 Mozilla::CA LWP::Protocol::https IO::Socket::SSL Digest::SHA

# Or using cpan
cpan LWP::UserAgent JSON HTTP::Request::Common File::Basename MIME::Base64 Mozilla::CA LWP::Protocol::https IO::Socket::SSL Digest::SHA

SSL Configuration: The client automatically configures SSL certificates. If you encounter SSL issues, run:

perl verify_ssl.pl

Alternative: install all dependencies from cpanfile

cpanm --installdeps .

Note: The SSL modules (Mozilla::CA, LWP::Protocol::https, IO::Socket::SSL) are required for secure HTTPS communication with the CCAI API.

Configuration

Environmental Variables

The CCAI Perl client supports loading credentials from environment variables using a .env file:

Step 1: Copy Example file:

cp .env.example .env

Step 2: Edit .env with your credentials:

# CCAI API Credentials
CCAI_CLIENT_ID=your-client-id-here
CCAI_API_KEY=your-api-key-here

# Optional: Suppress LWP Content-Length warnings (set to 1 to enable)
CCAI_SUPPRESS_WARNINGS=1

Step 3: Use your code:

# CCAI API Credentials
CCAI_CLIENT_ID=your-client-id-here
CCAI_API_KEY=your-api-key-here

# Optional: Suppress LWP Content-Length warnings (set to 1 to enable)
CCAI_SUPPRESS_WARNINGS=1

Direct Configuration

You can also configure credentials directly into your code:

use CCAI;

my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});


2. Send SMS message

#Update YOUR-CLIENT-ID and API-KEY-TOKEN 
#Update the account object to send messages to a valid phone number. 

use lib '.';
use CCAI;

# Initialize the client
my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});

# Send an SMS to multiple recipients
my @accounts = (
    {
        firstName => "John",
        lastName  => "Doe",
        phone      => "+15551234567"
    },
    {
        firstName => "Jane",
        lastName  => "Smith", 
        phone      => "+15559876543"
    }
);

my $response = $ccai->sms->send(
    \@accounts,
    "Hello \${firstName} \${lastName}, this is a test message!",
    "Test Campaign"
);

if ($response->{success}) {
    print "SMS sent successfully! Campaign ID: " . $response->{data}->{campaign_id} . "\n";
} else {
    print "Error: " . $response->{error} . "\n";
}

# Send an SMS to a single recipient
my $single_response = $ccai->sms->send_single(
    "Jane",
    "Smith",
    "+15559876543",
    "Hi \${firstName}, thanks for your interest!",
    "Single Message Test"
);

if ($single_response->{success}) {
    print "Single SMS sent successfully!\n";
} else {
    print "Error: " . $single_response->{error} . "\n";
    }
    

3. Webhooks with CustomData

use lib '.';
use CCAI;

# Initialize the client
my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});

# Process a webhook event with customData (in your webhook handler)
sub process_webhook_event {
    my ($json, $signature, $secret) = @_;
    
    # Verify the signature
    if ($ccai->webhook->verify_signature($signature, $json, $secret)) {
        # Parse the event
        my $event = $ccai->webhook->parse_event($json);
        
        if ($event && $event->{type} eq "message.sent") {
            print "Message sent to: $event->{to}\n";
            
            # Access customData for business logic
            if ($event->{customData}) {
                my $custom = $event->{customData};
                
                # Handle order-related messages
                if ($custom->{order_id}) {
                    print "Order notification sent: $custom->{order_id}\n";
                    # Update order status in your system
                    # update_order_notification_status($custom->{order_id}, 'sent');
                }
                
                # Handle appointment reminders
                if ($custom->{appointment_id}) {
                    print "Appointment reminder sent: $custom->{appointment_id}\n";
                    # Mark reminder as delivered
                    # mark_reminder_delivered($custom->{appointment_id});
                }
                
                # Customer segmentation
                if ($custom->{customer_type} eq 'premium') {
                    print "Premium customer notification delivered\n";
                    # Special handling for premium customers
                }
            }
        } elsif ($event && $event->{type} eq "message.failed") {
            print "Message failed to: $event->{to}\n";
            
            # Handle failures with context from customData
            if ($event->{customData} && $event->{customData}->{order_id}) {
                print "Order notification failed: " . $event->{customData}->{order_id} . "\n";
                # Implement fallback notification (email, etc.)
                # fallback_notification($event->{customData}->{order_id});
            }
        }
    } else {
        print "Invalid signature\n";
    }
}
### Basic SMS

```perl
use CCAI;

# Initialize the client
my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});

# Send an SMS to multiple recipients
my @accounts = (
    {
        firstName => "John",
        lastName  => "Doe",
        phone      => "+15551234567"
    },
    {
        firstName => "Jane",
        lastName  => "Smith", 
        phone      => "+15559876543"
    }
);

my $response = $ccai->sms->send(
    \@accounts,
    "Hello \${firstName} \${lastName}, this is a test message!",
    "Test Campaign"
);

if ($response->{success}) {
    print "SMS sent successfully! Campaign ID: " . $response->{data}->{campaign_id} . "\n";
} else {
    print "Error: " . $response->{error} . "\n";
}

# Send an SMS to a single recipient
my $single_response = $ccai->sms->send_single(
    "Jane",
    "Smith",
    "+15559876543",
    "Hi \${firstName}, thanks for your interest!",
    "Single Message Test"
);

if ($single_response->{success}) {
    print "Single SMS sent successfully!\n";
} else {
    print "Error: " . $single_response->{error} . "\n";
}


4. Send MMS Message

#Update YOUR-CLIENT-ID and API-KEY-TOKEN 
#Update the account object to send messages to a valid phone number. 
#Update path/to/your/image.jpg to be an actual jpg file

use lib '.';
use CCAI;

# Initialize the client
my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});

# Define progress callback
my $progress_callback = sub {
    my $status = shift;
    print "Progress: $status\n";
};

# Create options with progress tracking
my $options = {
    timeout     => 60000,
    on_progress => $progress_callback
};

# Complete MMS workflow (get URL, upload image, send MMS)
sub send_mms_with_image {
    # Path to your image file
    my $image_path = 'path/to/your/image.jpg';
    my $content_type = 'image/jpeg';
    
    # Define recipient
    my @accounts = ({
        firstName => 'John',
        lastName  => 'Doe',
        phone      => '+15551234567'
    });
    
    # Send MMS with image in one step
    my $response = $ccai->mms->send_with_image(
        $image_path,
        $content_type,
        \@accounts,
        "Hello \${firstName}, check out this image!",
        "MMS Campaign Example",
        $options
    );
    
    if ($response->{success}) {
        print "MMS sent! Campaign ID: " . $response->{data}->{campaign_id} . "\n";
    } else {
        print "Error sending MMS: " . $response->{error} . "\n";
    }
}

# Call the function
send_mms_with_image();

5. Sending an Email

use lib '.';
use CCAI;


# Initialize the client


my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});


# Send a single email


my $response = $ccai->email->send_single(
    "John",                                    # First name
    "Doe",                                     # Last name
    "[email protected]",                        # Email address
    "Welcome to Our Service",                  # Subject
    "<p>Hello \${firstName},</p><p>Thank you for signing up!</p>",  # HTML message content
    "[email protected]",                 # Sender email
    "[email protected]",                 # Reply-to email
    "Your Company",                            # Sender name
    "Welcome Email"                            # Campaign title
);

if ($response->{success}) {
    print "Email sent successfully! ID: " . $response->{data}->{id} . "\n";
} else {
    print "Error: " . $response->{error} . "\n";
}


# Send an email campaign to multiple recipients


my $campaign = {
    subject => "Monthly Newsletter",
    title => "July 2025 Newsletter",
    message => "<h1>Monthly Newsletter - July 2025</h1><p>Hello \${firstName},</p>",
    sender_email => "[email protected]",
    reply_email => "[email protected]",
    sender_name => "Your Company Newsletter",
    accounts => [
        {
            firstName => "John",
            lastName => "Doe",
            email => "[email protected]"
        },
        {
            firstName => "Jane",
            lastName => "Smith",
            email => "[email protected]"
        }
    ],
    campaign_type => "EMAIL",
    add_to_list => "noList",
    contact_input => "accounts",
    from_type => "single",
    senders => []
};

my $campaign_response = $ccai->email->send_campaign($campaign);

if ($campaign_response->{success}) {
    print "Email campaign sent successfully! ID: " . $campaign_response->{data}->{id} . "\n";
} else {
    print "Error: " . $campaign_response->{error} . "\n";
    }

6. Using Webhooks

Unified Event Handler (Recommended)

The new unified event handler processes all CloudContact webhook event types with a single callback function:

use lib '.';
use CCAI;

# Initialize the client
my $ccai = CCAI->new({
    client_id => 'YOUR-CLIENT-ID',
    api_key   => 'API-KEY-TOKEN'
});

# Register a webhook for all event types
my $webhook_response = $ccai->webhook->register({
    url => "https://example.com/webhook",
    events => [
        "message.sent", 
        "message.incoming", 
        "message.excluded",
        "message.error.carrier",
        "message.error.cloudcontact"
    ],
    secret => "your-webhook-secret"
});

if ($webhook_response->{success}) {
    print "Webhook registered! ID: " . $webhook_response->{data}->{id} . "\n";
}

# Process webhook events with unified handler
sub process_webhook_event {
    my ($json, $signature, $secret) = @_;
    
    # Verify the signature
    unless ($ccai->webhook->verify_signature($signature, $json, $secret)) {
        print "❌ Invalid signature\n";
        return;
    }
    
    # Process event with unified handler
    my $success = $ccai->webhook->handle_event($json, sub {
        my ($event_type, $data) = @_;
        
        if ($event_type eq 'message.sent') {
            print "✅ Message delivered to $data->{To}\n";
            print "   💰 Cost: \$$data->{TotalPrice}\n" if $data->{TotalPrice};
            print "   📊 Segments: $data->{Segments}\n" if $data->{Segments};
            print "   📢 Campaign: $data->{CampaignTitle}\n" if $data->{CampaignTitle};
            
        } elsif ($event_type eq 'message.incoming') {
            print "📨 Reply from $data->{From}: $data->{Message}\n";
            print "   📢 Original Campaign: $data->{CampaignTitle}\n" if $data->{CampaignTitle};
            
        } elsif ($event_type eq 'message.excluded') {
            print "⚠️  Message excluded: $data->{ExcludedReason}\n";
            print "   📞 Target: $data->{To}\n";
            
        } elsif ($event_type eq 'message.error.carrier') {
            print "❌ Carrier error $data->{ErrorCode}: $data->{ErrorMessage}\n";
            print "   📞 Target: $data->{To}\n";
            
        } elsif ($event_type eq 'message.error.cloudcontact') {
            print "🚨 System error $data->{ErrorCode}: $data->{ErrorMessage}\n";
            print "   📞 Target: $data->{To}\n";
        }
        
        # Show custom data if present
        if ($data->{CustomData} && $data->{CustomData} ne '') {
            print "   📋 Custom Data: $data->{CustomData}\n";
        }
    });
    
    unless ($success) {
        print "❌ Failed to process webhook event\n";
    }
}
Supported Event Types
message.sent - Message successfully delivered to recipient

Includes pricing information (TotalPrice) and segment count (Segments)
Contains campaign details and custom data
message.incoming - Reply received from recipient

Contains the reply message and sender information
Links back to original campaign if applicable
message.excluded - Message excluded during campaign creation

Provides exclusion reason (duplicate phone, invalid format, etc.)
Helps track why certain contacts didn't receive messages
message.error.carrier - Carrier-level delivery failure

Contains carrier error codes (30008, 30007, etc.)
Indicates network or carrier-specific issues
message.error.cloudcontact - CloudContact system error

Contains CCAI error codes (CCAI-001, CCAI-002, etc.)
Indicates account or system configuration issues
Legacy Event Processing (Backward Compatible)
# Legacy method still supported for backward compatibility
sub process_webhook_event_legacy {
    my ($json, $signature, $secret) = @_;
    
    if ($ccai->webhook->verify_signature($signature, $json, $secret)) {
        my $event = $ccai->webhook->parse_event($json);
        
        if ($event && $event->{type} eq "message.sent") {
            print "Message sent to: $event->{To}\n";
        } elsif ($event && $event->{type} eq "message.incoming") {
            print "Message received from: $event->{From}\n";
        }
    }
}

Supported Event Types


  • message.sent - Message successfully delivered to recipient
    • Includes pricing information (TotalPrice) and segment count (Segments)
    • Contains campaign details and custom data
  • message.incoming - Reply received from recipient
    • Contains the reply message and sender information
    • Links back to original campaign if applicable
  • message.excluded - Message excluded during campaign creation
    • Provides exclusion reason (duplicate phone, invalid format, etc.)
    • Helps track why certain contacts didn't receive messages
  • message.error.carrier - Carrier-level delivery failure
    • Contains carrier error codes (30008, 30007, etc.)
    • Indicates network or carrier-specific issues
  • message.error.cloudcontact - CloudContact system error
    • Contains CCAI error codes (CCAI-001, CCAI-002, etc.)
    • Indicates account or system configuration issues

Legacy Event Processing (Backwards Compatibility)

# Legacy method still supported for backward compatibility
sub process_webhook_event_legacy {
    my ($json, $signature, $secret) = @_;
    
    if ($ccai->webhook->verify_signature($signature, $json, $secret)) {
        my $event = $ccai->webhook->parse_event($json);
        
        if ($event && $event->{type} eq "message.sent") {
            print "Message sent to: $event->{To}\n";
        } elsif ($event && $event->{type} eq "message.incoming") {
            print "Message received from: $event->{From}\n";
        }
    }
}

7. Project Structure

  • lib/ - Library modules
    • CCAI.pm - Main CCAI client class
    • CCAI/SMS.pm - SMS service class
    • CCAI/MMS.pm - MMS service class
    • CCAI/Email.pm - Email service class
    • CCAI/Webhook.pm - Webhook service class
  • examples/ - Example usage scripts
  • t/ - Test files
  • cpanfile - Dependency specification

8. Development

Prerequisites

  • Perl 5.16.0 or higher
  • cpanm or cpan for installing dependencies

Setup

  1. Clone the repository
  2. Install dependencies:
cpanm --installdeps
  1. Run examples:
\perl examples/sms_example.pl

Testing

Run tests with prove:

# Run all tests
prove -l t/

# Run tests with verbose output
prove -lv t/

# Run specific test file
prove -lv t/01-basic.t

9. Features

  • Object-oriented Perl interface
  • Support for sending SMS to multiple recipients
  • Support for sending MMS with images
  • Support for sending email campaigns
  • Support for managing webhooks
  • Upload images to S3 with signed URLs
  • Support for template variables (firstName, lastName)
  • Progress tracking via callbacks
  • Comprehensive error handling
  • Unit tests
  • Modern Perl practices
  • Automatic SSL certificate configuration

10. SSL Certificate Handling

The CCAI client automatically configures SSL certificates using the Mozilla::CA module. If you encounter SSL certificate errors:

  • Automatic (Recommended): The client handles this automatically when Mozilla::CA is installed
  • Manual: Set the environment variable:
* export PERL_LWP_SSL_CA_FILE=$(perl -MMozilla::CA -e 'print Mozilla::CA::SSL_ca_file()')
* <br />
  • System CA: On some systems, you might need:
export PERL_LWP_SSL_CA_FILE=/etc/ssl/certs/ca-certificates.crt

11. Warning Suppression

The CCAI client may show harmless "Content-Length header value was wrong, fixed" warnings from LWP::UserAgent. These warnings don't affect functionality but can be suppressed:

Method 1: Environment Variable (Recommended)

# in your .env file:
CCAI_SUPPRESS_WARNINGS=1

Method 2: Programmatically

my $ccai = CCAI->new({
    client_id => $client_id,
    api_key   => $api_key
});

# Suppress warnings after creating the client
$ccai->suppress_lwp_warnings();

Method 3: In Your Script

# At the beginning of your script
BEGIN {
    $SIG{__WARN__} = sub {
        my $warning = shift;
        return if $warning =~ /Content-Length header value was wrong, fixed/;
        warn $warning;
    };
}

12. Error Handling

All methods return a hash reference with the following structure:

# Success response
{
    success => 1,
    data    => { ... }  # API response data
}

# Error response
{
    success => 0,
    error   => "Error message"
}

13. Template Variables

Messages support template variables that are automatically replaced:

  • ${firstName} Fill in with the contact's first name
  • ${lastName} Fill in with the contact's last nam

Example:

my $message = "Hello \${firstName} \${lastName}, welcome!";
# For John Doe, becomes: "Hello John Doe, welcome!"

Try it yourself

See the full source code here.