Skip to content

Commit a2f00a7

Browse files
authored
Complete Campaign API support (#48)
1 parent 055f5a7 commit a2f00a7

11 files changed

Lines changed: 497 additions & 1 deletion

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,28 @@ var phonebook = await client.Campaigns.CreatePhonebookAsync(new CreatePhonebookR
285285
});
286286
```
287287

288+
Send a campaign to a phonebook:
289+
290+
```csharp
291+
var campaign = await client.Campaigns.SendAsync(new SendCampaignRequest
292+
{
293+
CountryCode = "234",
294+
SenderId = "Termii",
295+
Message = "Welcome to Termii.",
296+
Channel = "generic",
297+
MessageType = "Plain",
298+
PhonebookId = "phonebook-123",
299+
CampaignType = "regular",
300+
ScheduleSmsStatus = "regular"
301+
});
302+
```
303+
304+
Fetch campaign history:
305+
306+
```csharp
307+
var history = await client.Campaigns.GetCampaignHistoryAsync("C714360330258");
308+
```
309+
288310
## Contacts
289311

290312
Add a single contact to a phonebook:
@@ -362,7 +384,7 @@ Implemented in the current SDK:
362384
- Number API: send message through a dedicated Termii number.
363385
- Tokens: send, verify, generate, voice, email, and WhatsApp OTP flows.
364386
- Insights: balance, DND status, number intelligence, message history, and message analytics.
365-
- Campaigns: list, create, update, and delete phonebooks.
387+
- Campaigns: send campaigns, list campaigns, fetch campaign history, retry failed campaigns, and manage phonebooks.
366388
- Contacts: list, add, upload, and delete phonebook contacts.
367389
- Product emails: send template-based notification emails.
368390

docs/API_COVERAGE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ The Termii docs describe a REST/JSON API and state that each account has its own
6060
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
6161
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
6262
| Insights | Fetch message analytics/history | GET | `/api/sms/history/analytics` | Query | `TermiiClient.Insights` | Implemented | #34, PR #35 | Unit tests added |
63+
| Campaigns | Send campaign | POST | `/api/sms/campaigns/send` | JSON body | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
64+
| Campaigns | Fetch campaigns | GET | `/api/sms/campaigns` | Query | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
65+
| Campaigns | Fetch campaign history | GET | `/api/sms/campaigns/{campaign_id}` | Query | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
66+
| Campaigns | Retry failed campaign | PATCH | `/api/sms/campaigns/{campaign_id}` | JSON body | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
6367
| Campaigns | Fetch phonebooks | GET | `/api/phonebooks` | Query | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
6468
| Campaigns | Create phonebook | POST | `/api/phonebooks` | JSON body | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
6569
| Campaigns | Update phonebook | PATCH | `/api/phonebooks/{phonebook_id}` | JSON body | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Termii;
4+
5+
public sealed class CampaignHistoryResponse
6+
{
7+
public string? Id { get; set; }
8+
9+
public int? ApplicationId { get; set; }
10+
11+
public string? Uuid { get; set; }
12+
13+
public string? CreatedAt { get; set; }
14+
15+
public string? UpdatedAt { get; set; }
16+
17+
public string? CampaignId { get; set; }
18+
19+
public string? PhonebookId { get; set; }
20+
21+
public string? PhonebookName { get; set; }
22+
23+
public string? Sender { get; set; }
24+
25+
public string? Message { get; set; }
26+
27+
public string? CountryCode { get; set; }
28+
29+
public string? SmsType { get; set; }
30+
31+
public string? CampaignType { get; set; }
32+
33+
public string? Status { get; set; }
34+
35+
public decimal? Cost { get; set; }
36+
37+
public int? TotalRecipient { get; set; }
38+
39+
public int? TotalDelivered { get; set; }
40+
41+
public int? TotalFailed { get; set; }
42+
43+
public int? Sent { get; set; }
44+
45+
public string? RunAt { get; set; }
46+
47+
public bool? IsLinkTrackingEnabled { get; set; }
48+
49+
public bool? Rerun { get; set; }
50+
51+
public string? SendBy { get; set; }
52+
53+
public bool? Personalized { get; set; }
54+
55+
[JsonExtensionData]
56+
public Dictionary<string, System.Text.Json.JsonElement>? AdditionalData { get; set; }
57+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Termii;
4+
5+
public sealed class CampaignListResponse
6+
{
7+
public IReadOnlyList<CampaignRecord> Content { get; set; } = Array.Empty<CampaignRecord>();
8+
9+
public int? TotalPages { get; set; }
10+
11+
public int? TotalElements { get; set; }
12+
13+
public int? Size { get; set; }
14+
15+
public int? Number { get; set; }
16+
17+
public bool? First { get; set; }
18+
19+
public bool? Last { get; set; }
20+
21+
public bool? Empty { get; set; }
22+
}
23+
24+
public sealed class CampaignRecord
25+
{
26+
[JsonPropertyName("campaign_id")]
27+
public string? CampaignId { get; set; }
28+
29+
[JsonPropertyName("run_at")]
30+
public string? RunAt { get; set; }
31+
32+
public string? Status { get; set; }
33+
34+
[JsonPropertyName("created_at")]
35+
public long? CreatedAt { get; set; }
36+
37+
[JsonPropertyName("phone_book")]
38+
public string? Phonebook { get; set; }
39+
40+
[JsonPropertyName("camp_type")]
41+
public string? CampaignType { get; set; }
42+
43+
[JsonPropertyName("total_recipients")]
44+
public int? TotalRecipients { get; set; }
45+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Termii;
2+
3+
public sealed class CampaignOperationResponse
4+
{
5+
public string? Message { get; set; }
6+
7+
public string? Status { get; set; }
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Termii;
2+
3+
public sealed class GetCampaignsRequest
4+
{
5+
public int? Page { get; set; }
6+
7+
public int? Size { get; set; }
8+
9+
internal string ToPath()
10+
{
11+
var query = new List<string>();
12+
TermiiCampaignQueryString.AddIfPresent(query, "page", Page);
13+
TermiiCampaignQueryString.AddIfPresent(query, "size", Size);
14+
15+
return TermiiCampaignQueryString.Append("/api/sms/campaigns", query);
16+
}
17+
}

src/Termii/Campaigns/ITermiiCampaignClient.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ namespace Termii;
22

33
public interface ITermiiCampaignClient
44
{
5+
Task<SendCampaignResponse> SendAsync(
6+
SendCampaignRequest request,
7+
CancellationToken cancellationToken = default);
8+
9+
Task<CampaignListResponse> GetCampaignsAsync(
10+
GetCampaignsRequest? request = null,
11+
CancellationToken cancellationToken = default);
12+
13+
Task<CampaignHistoryResponse> GetCampaignHistoryAsync(
14+
string campaignId,
15+
CancellationToken cancellationToken = default);
16+
17+
Task<CampaignOperationResponse> RetryAsync(
18+
string campaignId,
19+
CancellationToken cancellationToken = default);
20+
521
Task<PhonebookListResponse> GetPhonebooksAsync(
622
GetPhonebooksRequest? request = null,
723
CancellationToken cancellationToken = default);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Termii;
4+
5+
public sealed class SendCampaignRequest
6+
{
7+
[JsonPropertyName("country_code")]
8+
public string CountryCode { get; set; } = string.Empty;
9+
10+
[JsonPropertyName("sender_id")]
11+
public string SenderId { get; set; } = string.Empty;
12+
13+
[JsonPropertyName("message")]
14+
public string Message { get; set; } = string.Empty;
15+
16+
[JsonPropertyName("channel")]
17+
public string Channel { get; set; } = string.Empty;
18+
19+
[JsonPropertyName("message_type")]
20+
public string MessageType { get; set; } = string.Empty;
21+
22+
[JsonPropertyName("phonebook_id")]
23+
public string PhonebookId { get; set; } = string.Empty;
24+
25+
[JsonPropertyName("enable_link_tracking")]
26+
public bool? EnableLinkTracking { get; set; }
27+
28+
[JsonPropertyName("campaign_type")]
29+
public string CampaignType { get; set; } = string.Empty;
30+
31+
[JsonPropertyName("schedule_sms_status")]
32+
public string ScheduleSmsStatus { get; set; } = string.Empty;
33+
34+
[JsonPropertyName("schedule_time")]
35+
public string? ScheduleTime { get; set; }
36+
37+
[JsonPropertyName("delimiter")]
38+
public string? Delimiter { get; set; }
39+
40+
[JsonPropertyName("remove_duplicate")]
41+
public string? RemoveDuplicate { get; set; }
42+
43+
internal void Validate()
44+
{
45+
TermiiRequestValidation.Required(CountryCode, nameof(CountryCode));
46+
TermiiRequestValidation.Required(SenderId, nameof(SenderId));
47+
TermiiRequestValidation.Required(Message, nameof(Message));
48+
TermiiRequestValidation.Required(Channel, nameof(Channel));
49+
TermiiRequestValidation.Required(MessageType, nameof(MessageType));
50+
TermiiRequestValidation.Required(PhonebookId, nameof(PhonebookId));
51+
TermiiRequestValidation.Required(CampaignType, nameof(CampaignType));
52+
TermiiRequestValidation.Required(ScheduleSmsStatus, nameof(ScheduleSmsStatus));
53+
}
54+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Termii;
4+
5+
public sealed class SendCampaignResponse
6+
{
7+
public string? Message { get; set; }
8+
9+
[JsonPropertyName("campaignId")]
10+
public string? CampaignId { get; set; }
11+
12+
public string? Status { get; set; }
13+
}

src/Termii/Campaigns/TermiiCampaignClient.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,65 @@ public TermiiCampaignClient(TermiiJsonHttpPipeline pipeline)
1111
_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline));
1212
}
1313

14+
public Task<SendCampaignResponse> SendAsync(
15+
SendCampaignRequest request,
16+
CancellationToken cancellationToken = default)
17+
{
18+
if (request is null)
19+
{
20+
throw new ArgumentNullException(nameof(request));
21+
}
22+
23+
request.Validate();
24+
25+
return _pipeline.SendJsonAsync<SendCampaignResponse>(
26+
HttpMethod.Post,
27+
"/api/sms/campaigns/send",
28+
request,
29+
TermiiAuthenticationLocation.Body,
30+
cancellationToken);
31+
}
32+
33+
public Task<CampaignListResponse> GetCampaignsAsync(
34+
GetCampaignsRequest? request = null,
35+
CancellationToken cancellationToken = default)
36+
{
37+
return _pipeline.SendJsonAsync<CampaignListResponse>(
38+
HttpMethod.Get,
39+
(request ?? new GetCampaignsRequest()).ToPath(),
40+
body: null,
41+
TermiiAuthenticationLocation.Query,
42+
cancellationToken);
43+
}
44+
45+
public Task<CampaignHistoryResponse> GetCampaignHistoryAsync(
46+
string campaignId,
47+
CancellationToken cancellationToken = default)
48+
{
49+
TermiiRequestValidation.Required(campaignId, nameof(campaignId));
50+
51+
return _pipeline.SendJsonAsync<CampaignHistoryResponse>(
52+
HttpMethod.Get,
53+
$"/api/sms/campaigns/{Uri.EscapeDataString(campaignId)}",
54+
body: null,
55+
TermiiAuthenticationLocation.Query,
56+
cancellationToken);
57+
}
58+
59+
public Task<CampaignOperationResponse> RetryAsync(
60+
string campaignId,
61+
CancellationToken cancellationToken = default)
62+
{
63+
TermiiRequestValidation.Required(campaignId, nameof(campaignId));
64+
65+
return _pipeline.SendJsonAsync<CampaignOperationResponse>(
66+
Patch,
67+
$"/api/sms/campaigns/{Uri.EscapeDataString(campaignId)}",
68+
body: null,
69+
TermiiAuthenticationLocation.Body,
70+
cancellationToken);
71+
}
72+
1473
public Task<PhonebookListResponse> GetPhonebooksAsync(
1574
GetPhonebooksRequest? request = null,
1675
CancellationToken cancellationToken = default)

0 commit comments

Comments
 (0)