C#

Send SMS with C#

Learn how to send your first SMS using the CCAI C# 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 C# SDK

dotnet add package CloudContactAI.CCAI.NET

2. Send SMS message

using CCAI.NET;
using CCAI.NET.SMS;

// Initialize the client
var config = new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
};

using var ccai = new CCAIClient(config);

// Send a single SMS
var response = await ccai.SMS.SendSingleAsync(
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, this is a test message!",
    title: "Test Campaign"
);

Console.WriteLine($"Message sent with ID: {response.Id}");

// Send to multiple recipients
var accounts = new List<Account>
{
    new Account
    {
        FirstName = "John",
        LastName = "Doe",
        Phone = "+15551234567"
    },
    new Account
    {
        FirstName = "Jane",
        LastName = "Smith",
        Phone = "+15559876543"
    }
};

var campaignResponse = await ccai.SMS.SendAsync(
    accounts: accounts,
    message: "Hello ${FirstName} ${LastName}, this is a test message!",
    title: "Bulk Test Campaign"
);

Console.WriteLine($"Campaign sent with ID: {campaignResponse.CampaignId}");

3. Email Usage

using CCAI.NET;
using CCAI.NET.Email;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Send a single email
var response = await ccai.Email.SendSingleAsync(
    firstName: "John",
    lastName: "Doe",
    email: "[email protected]",
    subject: "Welcome to Our Service",
    message: "<p>Hello ${FirstName},</p><p>Thank you for signing up!</p>",
    senderEmail: "[email protected]",
    replyEmail: "[email protected]",
    senderName: "Your Company",
    title: "Welcome Email"
);

Console.WriteLine($"Email sent with ID: {response.Id}");

// Send to multiple recipients
var emailAccounts = new List<EmailAccount>
{
    new EmailAccount
    {
        FirstName = "John",
        LastName = "Doe",
        Email = "[email protected]"
    },
    new EmailAccount
    {
        FirstName = "Jane",
        LastName = "Smith",
        Email = "[email protected]"
    }
};

var campaign = new EmailCampaign
{
    Subject = "Monthly Newsletter",
    Title = "July 2025 Newsletter",
    Message = @"
        <h1>Monthly Newsletter - July 2025</h1>
        <p>Hello ${FirstName},</p>
        <p>Here are our updates for this month...</p>
    ",
    SenderEmail = "[email protected]",
    ReplyEmail = "[email protected]",
    SenderName = "Your Company Newsletter",
    Accounts = emailAccounts,
    CampaignType = "EMAIL",
    AddToList = "noList",
    ContactInput = "accounts",
    FromType = "single",
    Senders = new List<object>()
};

var campaignResponse = await ccai.Email.SendCampaignAsync(
    campaign: campaign,
    options: new EmailOptions
    {
        OnProgress = status => Console.WriteLine($"Progress: {status}")
    }
);

Console.WriteLine($"Email campaign sent with ID: {campaignResponse.Id}");

4. Scheduling an Email

// Schedule for tomorrow at 10:00 AM
var tomorrow = DateTime.Now.AddDays(1).Date.AddHours(10);

var scheduledCampaign = new EmailCampaign
{
    Subject = "Upcoming Event Reminder",
    Title = "Event Reminder Campaign",
    Message = @"
        <h1>Reminder: Upcoming Event</h1>
        <p>Hello ${FirstName},</p>
        <p>This is a reminder about our upcoming event tomorrow.</p>
    ",
    SenderEmail = "[email protected]",
    ReplyEmail = "[email protected]",
    SenderName = "Your Company Events",
    Accounts = emailAccounts,
    ScheduledTimestamp = tomorrow.ToString("o"), // ISO 8601 format
    ScheduledTimezone = "America/New_York"
};

var scheduledResponse = await ccai.Email.SendCampaignAsync(scheduledCampaign);
Console.WriteLine($"Email campaign scheduled with ID: {scheduledResponse.Id}");

5. Webhook Management

CloudContact Webhook Events (New Format)

CloudContact now sends webhook notifications with a consistent structure for all event types:

using CCAI.NET;
using CCAI.NET.Webhook;

// Parse CloudContact webhook event
var cloudContactEvent = ccai.Webhook.ParseCloudContactEvent(json);

switch (cloudContactEvent.EventType)
{
    case "message.sent":
        Console.WriteLine($"✅ Message delivered to {cloudContactEvent.Data.To}");
        Console.WriteLine($"   Cost: ${cloudContactEvent.Data.TotalPrice}");
        Console.WriteLine($"   Segments: {cloudContactEvent.Data.Segments}");
        break;
        
    case "message.incoming":
        Console.WriteLine($"📨 Reply from {cloudContactEvent.Data.From}: {cloudContactEvent.Data.Message}");
        break;
        
    case "message.excluded":
        Console.WriteLine($"⚠️ Message excluded: {cloudContactEvent.Data.ExcludedReason}");
        break;
        
    case "message.error.carrier":
        Console.WriteLine($"❌ Carrier error {cloudContactEvent.Data.ErrorCode}: {cloudContactEvent.Data.ErrorMessage}");
        break;
        
    case "message.error.cloudcontact":
        Console.WriteLine($"🚨 System error {cloudContactEvent.Data.ErrorCode}: {cloudContactEvent.Data.ErrorMessage}");
        break;
}

Supported Event Types

  • message.sent - Message successfully delivered to recipient
  • message.incoming - Reply received from recipient
  • message.excluded - Message excluded during campaign (duplicates, invalid numbers, etc.)
  • message.error.carrier - Carrier-level delivery failure
  • message.error.cloudcontact - CloudContact system error

ASP.NET Core Webhook Endpoint

[ApiController]
[Route("api/[controller]")]
public class WebhookController : ControllerBase
{
    [HttpPost("cloudcontact")]
    public async Task<IActionResult> HandleCloudContactWebhook()
    {
        using var reader = new StreamReader(Request.Body, Encoding.UTF8);
        var body = await reader.ReadToEndAsync();
        
        var webhookService = new WebhookService(null!);
        var cloudContactEvent = webhookService.ParseCloudContactEvent(body);
        
        // Process the event
        await ProcessWebhookEvent(cloudContactEvent);
        
        return Ok(new { status = "success" });
    }
}

Legacy Webhook Format (Backward Compatibility)

The library still supports the original webhook format:

using CCAI.NET;
using CCAI.NET.Webhook;
using DotNetEnv;

// Load environment variables
Env.Load();

// Initialize the client
var config = new CCAIConfig
{
    ClientId = Environment.GetEnvironmentVariable("CCAI_CLIENT_ID") ?? throw new InvalidOperationException("CCAI_CLIENT_ID not found"),
    ApiKey = Environment.GetEnvironmentVariable("CCAI_API_KEY") ?? throw new InvalidOperationException("CCAI_API_KEY not found")
};

using var ccai = new CCAIClient(config);

// Register a webhook
var webhookConfig = new WebhookConfig
{
    Url = "https://your-webhook-endpoint.com/webhook",
    Events = new List<WebhookEventType>
    {
        WebhookEventType.MessageSent,
        WebhookEventType.MessageIncoming,
        WebhookEventType.MessageExcluded,
        WebhookEventType.MessageErrorCarrier,
        WebhookEventType.MessageErrorCloudContact
    },
    Secret = "your-webhook-secret"
};

var registration = await ccai.Webhook.RegisterAsync(webhookConfig);
Console.WriteLine($"Webhook registered with ID: {registration.Id}");

// List all webhooks
var webhooks = await ccai.Webhook.ListAsync();
foreach (var webhook in webhooks)
{
    Console.WriteLine($"Webhook ID: {webhook.Id}, URL: {webhook.Url}");
}

// Update a webhook
var updatedConfig = new WebhookConfig
{
    Url = "https://your-updated-endpoint.com/webhook",
    Events = new List<WebhookEventType> { WebhookEventType.MessageSent },
    Secret = "your-updated-secret"
};

var updatedWebhook = await ccai.Webhook.UpdateAsync(registration.Id, updatedConfig);

// Delete a webhook
var deleteResponse = await ccai.Webhook.DeleteAsync(registration.Id);
Console.WriteLine($"Webhook deleted: {deleteResponse.Success}");

// Parse a webhook event (in your webhook handler)
public void ProcessWebhookEvent(string json, string signature, string secret)
{
    // Verify the signature
    if (ccai.Webhook.VerifySignature(signature, json, secret))
    {
        // Parse the event (supports both new and legacy formats)
        var webhookEvent = ccai.Webhook.ParseEvent(json);
        
        if (webhookEvent is MessageSentEvent sentEvent)
        {
            Console.WriteLine($"Message sent to: {sentEvent.To}");
        }
        else if (webhookEvent is MessageIncomingEvent incomingEvent)
        {
            Console.WriteLine($"Message received from: {incomingEvent.From}");
        }
    }
    else
    {
        Console.WriteLine("Invalid signature");
    }
}

6. Step-by-Step MMS Workflow

// Step 1: Get a signed URL for uploading
var uploadResponse = await ccai.MMS.GetSignedUploadUrlAsync(
    fileName: "image.jpg",
    fileType: "image/jpeg"
);

var signedUrl = uploadResponse.SignedS3Url;
var fileKey = uploadResponse.FileKey;

// Step 2: Upload the image to the signed URL
var uploadSuccess = await ccai.MMS.UploadImageToSignedUrlAsync(
    signedUrl: signedUrl,
    filePath: "path/to/your/image.jpg",
    contentType: "image/jpeg"
);

if (uploadSuccess)
{
    // Step 3: Send the MMS with the uploaded image
    var response = await ccai.MMS.SendAsync(
        pictureFileKey: fileKey,
        accounts: accounts,
        message: "Hello ${FirstName}, check out this image!",
        title: "MMS Campaign Example"
    );
    
    Console.WriteLine($"MMS sent! Campaign ID: {response.CampaignId}");
}

7. With Progress Tracking

// Create options with progress tracking
var options = new SMSOptions
{
    Timeout = 60,
    Retries = 3,
    OnProgress = status => Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {status}")
};

// Send SMS with progress tracking
var response = await ccai.SMS.SendAsync(
    accounts: accounts,
    message: message,
    title: title,
    options: options
);

8. Synchronous API

// Send a single SMS synchronously
var response = ccai.SMS.SendSingle(
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, this is a test message!",
    title: "Test Campaign"
);

// Send a single MMS synchronously
var mmsResponse = ccai.MMS.SendSingle(
    pictureFileKey: "your-client-id/campaign/image.jpg",
    firstName: "John",
    lastName: "Doe",
    phone: "+15551234567",
    message: "Hello ${FirstName}, check out this image!",
    title: "MMS Campaign"
);

// Send a single email synchronously
var emailResponse = ccai.Email.SendSingle(
    firstName: "John",
    lastName: "Doe",
    email: "[email protected]",
    subject: "Welcome to Our Service",
    message: "<p>Hello ${FirstName},</p><p>Thank you for signing up!</p>",
    senderEmail: "[email protected]",
    replyEmail: "[email protected]",
    senderName: "Your Company",
    title: "Welcome Email"
);

License

This project is licensed under the MIT License - see the LICENSE file for details.

9. Brand Registration

Register and manage brands for TCR verification.

using CCAI.NET;
using CCAI.NET.Brands;

var ccai = new CCAIClient(new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
});

// Create a brand
var brand = await ccai.Brands.CreateAsync(new BrandRequest
{
    LegalCompanyName = "Collect.org Inc.",
    Dba = "Collect",
    EntityType = "NON_PROFIT",
    TaxId = "123456789",
    TaxIdCountry = "US",
    Country = "US",
    VerticalType = "NON_PROFIT",
    WebsiteUrl = "https://www.collect.org",
    Street = "123 Main Street",
    City = "San Francisco",
    State = "CA",
    PostalCode = "94105",
    ContactFirstName = "Jane",
    ContactLastName = "Doe",
    ContactEmail = "[email protected]",
    ContactPhone = "+14155551234"
});
Console.WriteLine($"Brand created with ID: {brand.Id}");

// Get a brand by ID
var fetched = await ccai.Brands.GetAsync(brand.Id);
Console.WriteLine($"Brand name: {fetched.LegalCompanyName}");

// List all brands
var brands = await ccai.Brands.ListAsync();
Console.WriteLine($"Total brands: {brands.Length}");

// Update a brand (partial update)
await ccai.Brands.UpdateAsync(brand.Id, new BrandRequest
{
    Street = "456 Oak Avenue",
    City = "Los Angeles"
});

// Delete a brand
await ccai.Brands.DeleteAsync(brand.Id);

Entity Types: PRIVATE_PROFIT, PUBLIC_PROFIT, NON_PROFIT, GOVERNMENT, SOLE_PROPRIETOR

Vertical Types: AUTOMOTIVE, AGRICULTURE, BANKING, COMMUNICATION, CONSTRUCTION, EDUCATION, ENERGY, ENTERTAINMENT, GOVERNMENT, HEALTHCARE, HOSPITALITY, INSURANCE, LEGAL, MANUFACTURING, NON_PROFIT, PROFESSIONAL, REAL_ESTATE, RETAIL, TECHNOLOGY, TRANSPORTATION

10. Campaign Registration

Register and manage campaigns for TCR carrier vetting.

using CCAI.NET;
using CCAI.NET.Campaigns;

var ccai = new CCAIClient(new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
});

// Create a campaign
var campaign = await ccai.Campaigns.CreateAsync(new CampaignRequest
{
    BrandId = 1,
    UseCase = "MIXED",
    SubUseCases = new List<string> { "CUSTOMER_CARE", "TWO_FACTOR_AUTHENTICATION", "ACCOUNT_NOTIFICATION" },
    Description = "Security codes and support messaging.",
    MessageFlow = "Users opt-in via signup form at https://example.com/signup",
    HasEmbeddedLinks = true,
    HasEmbeddedPhone = false,
    IsAgeGated = false,
    IsDirectLending = false,
    OptInKeywords = new List<string> { "START" },
    OptInMessage = "Welcome! Reply STOP to cancel.",
    OptInProofUrl = "https://example.com/opt-in-proof.png",
    HelpKeywords = new List<string> { "HELP" },
    HelpMessage = "For HELP email [email protected].",
    OptOutKeywords = new List<string> { "STOP" },
    OptOutMessage = "STOP received. You are unsubscribed.",
    SampleMessages = new List<string>
    {
        "Your code is 554321. Reply STOP to cancel.",
        "Your ticket has been updated. Reply HELP for info."
    }
});
Console.WriteLine($"Campaign created with ID: {campaign.Id}");

// Get a campaign by ID
var fetched = await ccai.Campaigns.GetAsync(campaign.Id);
Console.WriteLine($"Campaign use case: {fetched.UseCase}");

// List all campaigns
var campaigns = await ccai.Campaigns.ListAsync();
Console.WriteLine($"Total campaigns: {campaigns.Length}");

// Update a campaign (partial update)
await ccai.Campaigns.UpdateAsync(campaign.Id, new CampaignRequest
{
    Description = "Updated description."
});

// Delete a campaign
await ccai.Campaigns.DeleteAsync(campaign.Id);

Use Cases: TWO_FACTOR_AUTHENTICATION, ACCOUNT_NOTIFICATION, CUSTOMER_CARE, DELIVERY_NOTIFICATION, FRAUD_ALERT, HIGHER_EDUCATION, LOW_VOLUME_MIXED, MARKETING, MIXED, POLLING_VOTING, PUBLIC_SERVICE_ANNOUNCEMENT, SECURITY_ALERT

MIXED and LOW_VOLUME_MIXED campaigns require 2–3 SubUseCases.

Sub-Use Cases: TWO_FACTOR_AUTHENTICATION, ACCOUNT_NOTIFICATION, CUSTOMER_CARE, DELIVERY_NOTIFICATION, FRAUD_ALERT, MARKETING, POLLING_VOTING

11. Contact Validator

Validate email addresses and phone numbers.

Bulk endpoints accept up to 50 contacts per request and are processed server-side in chunks.

using CCAI.NET;
using CCAI.NET.ContactValidator;

var config = new CCAIConfig
{
    ClientId = "YOUR-CLIENT-ID",
    ApiKey = "YOUR-API-KEY"
};

using var ccai = new CCAIClient(config);

// Validate a single email
var emailResult = await ccai.ContactValidator.ValidateEmailAsync("[email protected]");
Console.WriteLine($"Status: {emailResult.Status}"); // "valid" | "invalid" | "risky"

// Validate multiple emails (up to 50)
var bulkEmails = await ccai.ContactValidator.ValidateEmailsAsync(new[]
{
    "[email protected]",
    "[email protected]"
});
Console.WriteLine($"Total: {bulkEmails.Summary.Total}");  // 2
Console.WriteLine($"Valid: {bulkEmails.Summary.Valid}");   // 1

// Validate a single phone number
var phoneResult = await ccai.ContactValidator.ValidatePhoneAsync("+15551234567", "US");
Console.WriteLine($"Status: {phoneResult.Status}"); // "valid" | "invalid" | "landline"

// Validate multiple phone numbers (up to 50)
var bulkPhones = await ccai.ContactValidator.ValidatePhonesAsync(new[]
{
    new PhoneInput { Phone = "+15551234567" },
    new PhoneInput { Phone = "+15559876543", CountryCode = "US" }
});
Console.WriteLine($"Landline: {bulkPhones.Summary.Landline}"); // 1

12. Testing Webhook Installation

If you're testing the webhook installation, it's best to git clone the repository so you can get access to the examples/webhook-server project here.

Step 1: Install Ngrok

brew install ngrok

Step 2: Verify Ngrok

ngrok version

Step 3: Start the standalone webhook server

Open a new terminal window and run:

cd /Users/../CCAI.NET/examples/webhook-server
dotnet run

This will start a webhook server on http://localhost:3000

Step 4: In another terminal, start Ngrok

Open another terminal window and run:

ngrok http 3000
This will create a public tunnel to your local webhook server.

If you have not signed up for ngrok, you will need to:

ERROR: Sign up for an account: https://dashboard.ngrok.com/signup
ERROR: Install your authtoken: https://dashboard.ngrok.com/get-started/your-authtoken

Step 5: Get your Ngrok URL

ngrok will display something like:

Forwarding    https://abc123.ngrok.io -> http://localhost:3000
Copy that https://abc123.ngrok.io URL - this is your public webhook URL.

Example: https://81dbae920588.ngrok-free.app

Step 6: Configure CCAI with your Ngrok URL

  1. Log in to your CCAI account
  2. Navigate to the Settings\Integration tab
  3. Specify your ngrok url + '/webhook'

Step 7: Send a test SMS to trigger the webhook

cd /Users/../CCAI.NET/examples
dotnet run

Step 8: The Web server should receive the notification

Press Ctrl+C to stop the server

🔔 Received webhook event at root path! ⏰ Time: 2025-09-03 00:51:19 UTC 📋 Headers: Accept: application/json, application/*+json Host: 13c29ec4a161.ngrok-free.app User-Agent: Java/14-ea Accept-Encoding: gzip Content-Type: application/json Content-Length: 304 X-Forwarded-For: 159.65.99.19 X-Forwarded-Host: 13c29ec4a161.ngrok-free.app X-Forwarded-Proto: https 📄 Raw Body: {"eventType":"message.sent","data":{"id":142065,"MessageStatus":"SENT","To":"+14155551212","Message":"Hello John Doe, this is a test message!","CustomData":"","ClientExternalId":"a43c42c6-b0c1-45c5-b2fb-290ee7e6f113","CampaignId":141293,"CampaignTitle":"Default Campaign","Segments":1,"TotalPrice":0.03}}
🎯 Parsed CloudContact Event: Event Type: message.sent Message Status: SENT To: +14155551212 Message: Hello John Doe, this is a test message! ✅ Message delivered successfully! 💰 Cost: $0.0300 📊 Segments: 1 📢 Campaign: Default Campaign (ID: 141293) 🆔 External ID: a43c42c6-b0c1-45c5-b2fb-290ee7e6f113application/json;+charset=utf-8 137.6141ms

Step 9: From your phone, respond to the message

On your mobile phone, respond to the message that was sent to you by CCAI

Step 10: Web Server should receive the response notification

🎯 Parsed CloudContact Event: Event Type: message.sent Message Status: SENT To: +14155551212 Message: Hello John Doe, this is a test message! ✅ Message delivered successfully! 💰 Cost: $0.0300 📊 Segments: 1 📢 Campaign: Default Campaign (ID: 141293) 🆔 External ID: a43c42c6-b0c1-45c5-b2fb-290ee7e6f113application/json;+charset=utf-8 137.6141ms

Try it yourself

See the full source code here.


Did this page help you?