Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,28 @@ var phonebook = await client.Campaigns.CreatePhonebookAsync(new CreatePhonebookR
});
```

Send a campaign to a phonebook:

```csharp
var campaign = await client.Campaigns.SendAsync(new SendCampaignRequest
{
CountryCode = "234",
SenderId = "Termii",
Message = "Welcome to Termii.",
Channel = "generic",
MessageType = "Plain",
PhonebookId = "phonebook-123",
CampaignType = "regular",
ScheduleSmsStatus = "regular"
});
```

Fetch campaign history:

```csharp
var history = await client.Campaigns.GetCampaignHistoryAsync("C714360330258");
```

## Contacts

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

Expand Down
4 changes: 4 additions & 0 deletions docs/API_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ The Termii docs describe a REST/JSON API and state that each account has its own
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
| Insights | Fetch message analytics/history | GET | `/api/sms/history/analytics` | Query | `TermiiClient.Insights` | Implemented | #34, PR #35 | Unit tests added |
| Campaigns | Send campaign | POST | `/api/sms/campaigns/send` | JSON body | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
| Campaigns | Fetch campaigns | GET | `/api/sms/campaigns` | Query | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
| Campaigns | Fetch campaign history | GET | `/api/sms/campaigns/{campaign_id}` | Query | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
| Campaigns | Retry failed campaign | PATCH | `/api/sms/campaigns/{campaign_id}` | JSON body | `TermiiClient.Campaigns` | Implemented | #44 | Unit tests added |
| Campaigns | Fetch phonebooks | GET | `/api/phonebooks` | Query | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
| Campaigns | Create phonebook | POST | `/api/phonebooks` | JSON body | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
| Campaigns | Update phonebook | PATCH | `/api/phonebooks/{phonebook_id}` | JSON body | `TermiiClient.Campaigns` | Implemented | #33, PR #38 | Unit tests added |
Expand Down
57 changes: 57 additions & 0 deletions src/Termii/Campaigns/CampaignHistoryResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class CampaignHistoryResponse
{
public string? Id { get; set; }

public int? ApplicationId { get; set; }

public string? Uuid { get; set; }

public string? CreatedAt { get; set; }

public string? UpdatedAt { get; set; }

public string? CampaignId { get; set; }

public string? PhonebookId { get; set; }

public string? PhonebookName { get; set; }

public string? Sender { get; set; }

public string? Message { get; set; }

public string? CountryCode { get; set; }

public string? SmsType { get; set; }

public string? CampaignType { get; set; }

public string? Status { get; set; }

public decimal? Cost { get; set; }

public int? TotalRecipient { get; set; }

public int? TotalDelivered { get; set; }

public int? TotalFailed { get; set; }

public int? Sent { get; set; }

public string? RunAt { get; set; }

public bool? IsLinkTrackingEnabled { get; set; }

public bool? Rerun { get; set; }

public string? SendBy { get; set; }

public bool? Personalized { get; set; }

[JsonExtensionData]
public Dictionary<string, System.Text.Json.JsonElement>? AdditionalData { get; set; }
}
45 changes: 45 additions & 0 deletions src/Termii/Campaigns/CampaignListResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class CampaignListResponse
{
public IReadOnlyList<CampaignRecord> Content { get; set; } = Array.Empty<CampaignRecord>();

public int? TotalPages { get; set; }

public int? TotalElements { get; set; }

public int? Size { get; set; }

public int? Number { get; set; }

public bool? First { get; set; }

public bool? Last { get; set; }

public bool? Empty { get; set; }
}

public sealed class CampaignRecord
{
[JsonPropertyName("campaign_id")]
public string? CampaignId { get; set; }

[JsonPropertyName("run_at")]
public string? RunAt { get; set; }

public string? Status { get; set; }

[JsonPropertyName("created_at")]
public long? CreatedAt { get; set; }

[JsonPropertyName("phone_book")]
public string? Phonebook { get; set; }

[JsonPropertyName("camp_type")]
public string? CampaignType { get; set; }

[JsonPropertyName("total_recipients")]
public int? TotalRecipients { get; set; }
}
8 changes: 8 additions & 0 deletions src/Termii/Campaigns/CampaignOperationResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Termii;

public sealed class CampaignOperationResponse
{
public string? Message { get; set; }

public string? Status { get; set; }
}
17 changes: 17 additions & 0 deletions src/Termii/Campaigns/GetCampaignsRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Termii;

public sealed class GetCampaignsRequest
{
public int? Page { get; set; }

public int? Size { get; set; }

internal string ToPath()
{
var query = new List<string>();
TermiiCampaignQueryString.AddIfPresent(query, "page", Page);
TermiiCampaignQueryString.AddIfPresent(query, "size", Size);

return TermiiCampaignQueryString.Append("/api/sms/campaigns", query);
}
}
16 changes: 16 additions & 0 deletions src/Termii/Campaigns/ITermiiCampaignClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ namespace Termii;

public interface ITermiiCampaignClient
{
Task<SendCampaignResponse> SendAsync(
SendCampaignRequest request,
CancellationToken cancellationToken = default);

Task<CampaignListResponse> GetCampaignsAsync(
GetCampaignsRequest? request = null,
CancellationToken cancellationToken = default);

Task<CampaignHistoryResponse> GetCampaignHistoryAsync(
string campaignId,
CancellationToken cancellationToken = default);

Task<CampaignOperationResponse> RetryAsync(
string campaignId,
CancellationToken cancellationToken = default);

Task<PhonebookListResponse> GetPhonebooksAsync(
GetPhonebooksRequest? request = null,
CancellationToken cancellationToken = default);
Expand Down
54 changes: 54 additions & 0 deletions src/Termii/Campaigns/SendCampaignRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendCampaignRequest
{
[JsonPropertyName("country_code")]
public string CountryCode { get; set; } = string.Empty;

[JsonPropertyName("sender_id")]
public string SenderId { get; set; } = string.Empty;

[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;

[JsonPropertyName("channel")]
public string Channel { get; set; } = string.Empty;

[JsonPropertyName("message_type")]
public string MessageType { get; set; } = string.Empty;

[JsonPropertyName("phonebook_id")]
public string PhonebookId { get; set; } = string.Empty;

[JsonPropertyName("enable_link_tracking")]
public bool? EnableLinkTracking { get; set; }

[JsonPropertyName("campaign_type")]
public string CampaignType { get; set; } = string.Empty;

[JsonPropertyName("schedule_sms_status")]
public string ScheduleSmsStatus { get; set; } = string.Empty;

[JsonPropertyName("schedule_time")]
public string? ScheduleTime { get; set; }

[JsonPropertyName("delimiter")]
public string? Delimiter { get; set; }

[JsonPropertyName("remove_duplicate")]
public string? RemoveDuplicate { get; set; }

internal void Validate()
{
TermiiRequestValidation.Required(CountryCode, nameof(CountryCode));
TermiiRequestValidation.Required(SenderId, nameof(SenderId));
TermiiRequestValidation.Required(Message, nameof(Message));
TermiiRequestValidation.Required(Channel, nameof(Channel));
TermiiRequestValidation.Required(MessageType, nameof(MessageType));
TermiiRequestValidation.Required(PhonebookId, nameof(PhonebookId));
TermiiRequestValidation.Required(CampaignType, nameof(CampaignType));
TermiiRequestValidation.Required(ScheduleSmsStatus, nameof(ScheduleSmsStatus));
}
}
13 changes: 13 additions & 0 deletions src/Termii/Campaigns/SendCampaignResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendCampaignResponse
{
public string? Message { get; set; }

[JsonPropertyName("campaignId")]
public string? CampaignId { get; set; }

public string? Status { get; set; }
}
59 changes: 59 additions & 0 deletions src/Termii/Campaigns/TermiiCampaignClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,65 @@ public TermiiCampaignClient(TermiiJsonHttpPipeline pipeline)
_pipeline = pipeline ?? throw new ArgumentNullException(nameof(pipeline));
}

public Task<SendCampaignResponse> SendAsync(
SendCampaignRequest request,
CancellationToken cancellationToken = default)
{
if (request is null)
{
throw new ArgumentNullException(nameof(request));
}

request.Validate();

return _pipeline.SendJsonAsync<SendCampaignResponse>(
HttpMethod.Post,
"/api/sms/campaigns/send",
request,
TermiiAuthenticationLocation.Body,
cancellationToken);
}

public Task<CampaignListResponse> GetCampaignsAsync(
GetCampaignsRequest? request = null,
CancellationToken cancellationToken = default)
{
return _pipeline.SendJsonAsync<CampaignListResponse>(
HttpMethod.Get,
(request ?? new GetCampaignsRequest()).ToPath(),
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}

public Task<CampaignHistoryResponse> GetCampaignHistoryAsync(
string campaignId,
CancellationToken cancellationToken = default)
{
TermiiRequestValidation.Required(campaignId, nameof(campaignId));

return _pipeline.SendJsonAsync<CampaignHistoryResponse>(
HttpMethod.Get,
$"/api/sms/campaigns/{Uri.EscapeDataString(campaignId)}",
body: null,
TermiiAuthenticationLocation.Query,
cancellationToken);
}

public Task<CampaignOperationResponse> RetryAsync(
string campaignId,
CancellationToken cancellationToken = default)
{
TermiiRequestValidation.Required(campaignId, nameof(campaignId));

return _pipeline.SendJsonAsync<CampaignOperationResponse>(
Patch,
$"/api/sms/campaigns/{Uri.EscapeDataString(campaignId)}",
body: null,
TermiiAuthenticationLocation.Body,
cancellationToken);
}

public Task<PhonebookListResponse> GetPhonebooksAsync(
GetPhonebooksRequest? request = null,
CancellationToken cancellationToken = default)
Expand Down
Loading
Loading