diff --git a/CHANGELOG.md b/CHANGELOG.md
index 33443a7..22f4af6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.6.0] - 2026-08-17
+
+### Added
+- **Template-controlled SMS sending** (CLOUD-2885): optional `TemplateId` on `SMSCampaign`/`SMSRequest`, plus new `SMS.SendWithTemplateAsync()` and `SMS.SendSingleWithTemplateAsync()` methods for accounts that require a pre-approved template.
+
## [1.4.0] - 2025-09-03
### Added
diff --git a/integration/Program.cs b/integration/Program.cs
index 20b38cd..81379f0 100644
--- a/integration/Program.cs
+++ b/integration/Program.cs
@@ -1,6 +1,7 @@
-// .NET SDK integration tests — 52 tests
+// .NET SDK integration tests — 54 tests
// Covers: SMS (1-6), MMS (7-17), Email (18-22), Webhook (23-29), Contact (30-31),
-// Brands (32-36), Campaigns (37-42), ContactValidator (43-46), Negative cases (47-52)
+// Brands (32-36), Campaigns (37-42), ContactValidator (43-46), Negative cases (47-52),
+// SMS Templates (53-54)
//
// Test results use three states:
// PASS — the test ran and all assertions held
@@ -111,7 +112,7 @@ string WriteTempPng()
"CCAI_TEST_FIRST_NAME", "CCAI_TEST_LAST_NAME",
"CCAI_TEST_FIRST_NAME_2", "CCAI_TEST_LAST_NAME_2",
"CCAI_TEST_FIRST_NAME_3", "CCAI_TEST_LAST_NAME_3",
- "WEBHOOK_URL",
+ "WEBHOOK_URL", "CCAI_TEST_TEMPLATE_ID",
];
var missing = requiredEnv.Where(k => string.IsNullOrEmpty(Environment.GetEnvironmentVariable(k))).ToList();
if (missing.Count > 0)
@@ -134,6 +135,7 @@ string WriteTempPng()
var ln2 = Environment.GetEnvironmentVariable("CCAI_TEST_LAST_NAME_2")!;
var fn3 = Environment.GetEnvironmentVariable("CCAI_TEST_FIRST_NAME_3")!;
var ln3 = Environment.GetEnvironmentVariable("CCAI_TEST_LAST_NAME_3")!;
+var templateId = long.Parse(Environment.GetEnvironmentVariable("CCAI_TEST_TEMPLATE_ID")!);
// Unique per-run suffix so parallel SDK runs don't collide on the same webhook URL
var runId = $"dotnet-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
@@ -823,6 +825,24 @@ await Run("52 PERMISSIVE: MMS.SendAsync with nonexistent fileKey (API accepts)",
"nonexistent fileKey accepted", ".NET Permissive 52");
AssertSmsResponse(resp);
});
+
+ Console.WriteLine("\n--- SMS Templates ---");
+
+ await Run("53 SMS.SendWithTemplateAsync", async () =>
+ {
+ var resp = await client.SMS.SendWithTemplateAsync(
+ [
+ new Account { FirstName = fn1, LastName = ln1, Phone = phone1 },
+ new Account { FirstName = fn2, LastName = ln2, Phone = phone2 },
+ ], templateId, ".NET Template Test");
+ AssertSmsResponse(resp);
+ });
+
+ await Run("54 SMS.SendSingleWithTemplateAsync", async () =>
+ {
+ var resp = await client.SMS.SendSingleWithTemplateAsync(fn1, ln1, phone1, templateId, ".NET Single Template Test");
+ AssertSmsResponse(resp);
+ });
}
finally
{
diff --git a/src/CCAI.NET/CCAI.NET.csproj b/src/CCAI.NET/CCAI.NET.csproj
index 1dc26a5..eeb84c8 100644
--- a/src/CCAI.NET/CCAI.NET.csproj
+++ b/src/CCAI.NET/CCAI.NET.csproj
@@ -6,7 +6,7 @@
enable
latest
CloudContactAI.CCAI.NET
- 1.5.0
+ 1.6.0
CloudContactAI LLC
CloudContactAI LLC
C# client for CloudContactAI API with SMS, MMS, Email, and Webhook support. Enhanced webhook support with new CloudContact event format, contact.unsubscribed events, environment variable configuration, and comprehensive examples.
diff --git a/src/CCAI.NET/SMS/SMSService.cs b/src/CCAI.NET/SMS/SMSService.cs
index 43c98d0..210ae48 100644
--- a/src/CCAI.NET/SMS/SMSService.cs
+++ b/src/CCAI.NET/SMS/SMSService.cs
@@ -128,7 +128,7 @@ public async Task SendAsync(SMSRequest request, CancellationToken c
throw new ArgumentException("At least one account is required", nameof(request.Accounts));
}
- if (string.IsNullOrEmpty(request.Message))
+ if (string.IsNullOrEmpty(request.Message) && request.TemplateId is null)
{
throw new ArgumentException("Message is required", nameof(request.Message));
}