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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ var numberMessage = await client.Numbers.SendAsync(new SendNumberMessageRequest
});
```

Send an OTP token:

```csharp
var token = await client.Tokens.SendAsync(new SendTokenRequest
{
To = "2348012345678",
From = "Termii",
Channel = TermiiMessageChannel.Generic,
PinAttempts = 3,
PinTimeToLive = 10,
PinLength = 6,
PinPlaceholder = "< 123456 >",
MessageText = "Your verification code is < 123456 >"
});
```

## Examples

Run the examples project after setting your Termii API key:
Expand Down
15 changes: 8 additions & 7 deletions docs/API_COVERAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ The Termii docs describe a REST/JSON API and state that each account has its own
| Messaging | Send SMS/channel message | POST | `/api/sms/send` | JSON body | `TermiiClient.Messaging` | Implemented | #7, PR #21 | Unit tests added |
| Messaging | Send WhatsApp conversational message | POST | `/api/sms/send` | JSON body | `TermiiClient.Messaging` | Implemented | #7, PR #21 | Unit tests added |
| Messaging | Send bulk message | POST | `/api/sms/send/bulk` | JSON body | `TermiiClient.Messaging` | Implemented | #7, PR #21 | Unit tests added |
| Messaging | Send via Number API | POST | `/api/sms/number/send` | JSON body | `TermiiClient.Numbers` | In progress | #5 | Unit tests added |
| Token | Send OTP token | POST | `/api/sms/otp/send` | JSON body | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Token | Verify OTP token | POST | `/api/sms/otp/verify` | JSON body | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Token | Generate in-app OTP token | POST | `/api/sms/otp/generate` | JSON body | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Token | Send voice OTP/call token | POST | `/api/sms/otp/call` | JSON body | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Token | Send email OTP token | POST | `/api/email/otp/send` | JSON body | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Token | Send WhatsApp OTP token | POST | `/api/sms/send` | JSON body with `channel=whatsapp_otp` | `TermiiClient.Token` | Planned | #4 | Planned unit + optional integration |
| Messaging | Send via Number API | POST | `/api/sms/number/send` | JSON body | `TermiiClient.Numbers` | Implemented | #5, PR #25 | Unit tests added |
| Token | Send OTP token | POST | `/api/sms/otp/send` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Verify OTP token | POST | `/api/sms/otp/verify` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Generate in-app OTP token | POST | `/api/sms/otp/generate` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send voice OTP | POST | `/api/sms/otp/send/voice` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send voice call token | POST | `/api/sms/otp/call` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send email OTP token | POST | `/api/email/otp/send` | JSON body | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Token | Send WhatsApp OTP token | POST | `/api/sms/send` | JSON body with `channel=whatsapp_otp` | `TermiiClient.Tokens` | In progress | #4 | Unit tests added |
| Insights | Get balance | GET | `/api/get-balance` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Insights | Search DND/number status | GET | `/api/check/dnd` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | Planned | #9 | Planned unit + optional integration |
Expand Down
3 changes: 3 additions & 0 deletions src/Termii/TermiiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ private TermiiClient(HttpClient httpClient, TermiiOptions options, bool ownsHttp
Messaging = new TermiiMessagingClient(_pipeline);
SenderIds = new TermiiSenderIdClient(_pipeline);
Numbers = new TermiiNumberClient(_pipeline);
Tokens = new TermiiTokenClient(_pipeline);
}

public TermiiOptions Options { get; }
Expand All @@ -47,6 +48,8 @@ private TermiiClient(HttpClient httpClient, TermiiOptions options, bool ownsHttp

public ITermiiNumberClient Numbers { get; }

public ITermiiTokenClient Tokens { get; }

public void Dispose()
{
if (_ownsHttpClient)
Expand Down
19 changes: 19 additions & 0 deletions src/Termii/TermiiRequestValidation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,23 @@ public static void Required(string? value, string parameterName)
throw new ArgumentException($"{parameterName} is required.", parameterName);
}
}

public static void Positive(int value, string parameterName)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(parameterName, value, $"{parameterName} must be greater than zero.");
}
}

public static void Range(int value, int minimum, int maximum, string parameterName)
{
if (value < minimum || value > maximum)
{
throw new ArgumentOutOfRangeException(
parameterName,
value,
$"{parameterName} must be between {minimum} and {maximum}.");
}
}
}
29 changes: 29 additions & 0 deletions src/Termii/Tokens/GenerateTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class GenerateTokenRequest
{
[JsonIgnore]
public TermiiTokenPinType PinType { get; set; } = TermiiTokenPinType.Numeric;

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

[JsonPropertyName("pin_attempts")]
public int PinAttempts { get; set; }

[JsonPropertyName("pin_time_to_live")]
public int PinTimeToLive { get; set; }

[JsonPropertyName("pin_length")]
public int PinLength { get; set; }

internal void Validate()
{
TermiiRequestValidation.Required(PhoneNumber, nameof(PhoneNumber));
TermiiRequestValidation.Positive(PinAttempts, nameof(PinAttempts));
TermiiRequestValidation.Range(PinTimeToLive, 0, 60, nameof(PinTimeToLive));
TermiiRequestValidation.Range(PinLength, 4, 8, nameof(PinLength));
}
}
18 changes: 18 additions & 0 deletions src/Termii/Tokens/GenerateTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class GenerateTokenResponse
{
[JsonPropertyName("phone_number_other")]
public string? PhoneNumberOther { get; set; }

[JsonPropertyName("phone_number")]
public string? PhoneNumber { get; set; }

[JsonPropertyName("otp")]
public string? Otp { get; set; }

[JsonPropertyName("pin_id")]
public string? PinId { get; set; }
}
32 changes: 32 additions & 0 deletions src/Termii/Tokens/ITermiiTokenClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace Termii;

public interface ITermiiTokenClient
{
Task<SendTokenResponse> SendAsync(
SendTokenRequest request,
CancellationToken cancellationToken = default);

Task<VerifyTokenResponse> VerifyAsync(
VerifyTokenRequest request,
CancellationToken cancellationToken = default);

Task<GenerateTokenResponse> GenerateAsync(
GenerateTokenRequest request,
CancellationToken cancellationToken = default);

Task<TermiiMessageResponse> SendVoiceAsync(
SendVoiceTokenRequest request,
CancellationToken cancellationToken = default);

Task<TermiiMessageResponse> CallAsync(
VoiceCallTokenRequest request,
CancellationToken cancellationToken = default);

Task<TermiiMessageResponse> SendEmailAsync(
SendEmailTokenRequest request,
CancellationToken cancellationToken = default);

Task<TermiiMessageResponse> SendWhatsAppAsync(
SendWhatsAppTokenRequest request,
CancellationToken cancellationToken = default);
}
22 changes: 22 additions & 0 deletions src/Termii/Tokens/SendEmailTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendEmailTokenRequest
{
[JsonPropertyName("email_address")]
public string EmailAddress { get; set; } = string.Empty;

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

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

internal void Validate()
{
TermiiRequestValidation.Required(EmailAddress, nameof(EmailAddress));
TermiiRequestValidation.Required(Code, nameof(Code));
TermiiRequestValidation.Required(EmailConfigurationId, nameof(EmailConfigurationId));
}
}
44 changes: 44 additions & 0 deletions src/Termii/Tokens/SendTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendTokenRequest
{
[JsonIgnore]
public TermiiTokenPinType PinType { get; set; } = TermiiTokenPinType.Numeric;

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

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

[JsonIgnore]
public TermiiMessageChannel Channel { get; set; } = TermiiMessageChannel.Generic;

[JsonPropertyName("pin_attempts")]
public int PinAttempts { get; set; }

[JsonPropertyName("pin_time_to_live")]
public int PinTimeToLive { get; set; }

[JsonPropertyName("pin_length")]
public int PinLength { get; set; }

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

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

internal void Validate()
{
TermiiRequestValidation.Required(To, nameof(To));
TermiiRequestValidation.Required(From, nameof(From));
TermiiRequestValidation.Required(PinPlaceholder, nameof(PinPlaceholder));
TermiiRequestValidation.Required(MessageText, nameof(MessageText));
TermiiRequestValidation.Positive(PinAttempts, nameof(PinAttempts));
TermiiRequestValidation.Range(PinTimeToLive, 0, 60, nameof(PinTimeToLive));
TermiiRequestValidation.Range(PinLength, 4, 8, nameof(PinLength));
}
}
27 changes: 27 additions & 0 deletions src/Termii/Tokens/SendTokenResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendTokenResponse
{
[JsonPropertyName("smsStatus")]
public string? SmsStatus { get; set; }

[JsonPropertyName("phone_number")]
public string? PhoneNumber { get; set; }

[JsonPropertyName("to")]
public string? To { get; set; }

[JsonPropertyName("pinId")]
public string? PinId { get; set; }

[JsonPropertyName("pin_id")]
public string? PinIdSnakeCase { get; set; }

[JsonPropertyName("message_id_str")]
public string? MessageIdString { get; set; }

[JsonPropertyName("status")]
public string? Status { get; set; }
}
26 changes: 26 additions & 0 deletions src/Termii/Tokens/SendVoiceTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendVoiceTokenRequest
{
[JsonPropertyName("phone_number")]
public string PhoneNumber { get; set; } = string.Empty;

[JsonPropertyName("pin_attempts")]
public int PinAttempts { get; set; }

[JsonPropertyName("pin_time_to_live")]
public int PinTimeToLive { get; set; }

[JsonPropertyName("pin_length")]
public int PinLength { get; set; }

internal void Validate()
{
TermiiRequestValidation.Required(PhoneNumber, nameof(PhoneNumber));
TermiiRequestValidation.Positive(PinAttempts, nameof(PinAttempts));
TermiiRequestValidation.Range(PinTimeToLive, 0, 60, nameof(PinTimeToLive));
TermiiRequestValidation.Range(PinLength, 4, 8, nameof(PinLength));
}
}
25 changes: 25 additions & 0 deletions src/Termii/Tokens/SendWhatsAppTokenRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text.Json.Serialization;

namespace Termii;

public sealed class SendWhatsAppTokenRequest
{
[JsonPropertyName("to")]
public string To { get; set; } = string.Empty;

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

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

[JsonIgnore]
public TermiiMessageType Type { get; set; } = TermiiMessageType.Plain;

internal void Validate()
{
TermiiRequestValidation.Required(To, nameof(To));
TermiiRequestValidation.Required(From, nameof(From));
TermiiRequestValidation.Required(Sms, nameof(Sms));
}
}
Loading
Loading