Skip to content

Commit d70d1b0

Browse files
authored
Improve README developer experience (#29)
1 parent 654d4ee commit d70d1b0

3 files changed

Lines changed: 228 additions & 50 deletions

File tree

README.md

Lines changed: 200 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,45 @@
22

33
[![CI](https://github.com/teesofttech/Termii.SDK/actions/workflows/ci.yml/badge.svg)](https://github.com/teesofttech/Termii.SDK/actions/workflows/ci.yml)
44

5-
A .NET SDK for the Termii messaging, token, and insights APIs.
5+
A .NET SDK for the Termii messaging, sender ID, Number API, Token API, and Insights APIs.
66

7-
> This SDK is in early development. The first milestones establish the client, tests, examples, API coverage matrix, and package structure before endpoint support is added feature by feature.
7+
## Installation
88

9-
## Current Status
10-
11-
The initial repository setup includes:
12-
13-
- A `Termii` SDK project targeting `netstandard2.0` and `net8.0`.
14-
- A lightweight unit test project.
15-
- An examples project for developer-facing usage.
16-
- An API coverage matrix in `docs/API_COVERAGE.md`.
17-
18-
## Usage
19-
20-
Install the package:
9+
Install from NuGet:
2110

2211
```bash
2312
dotnet add package Termii.SDK
2413
```
2514

15+
Then import the SDK namespace:
16+
2617
```csharp
2718
using Termii;
19+
```
2820

21+
## Configuration
22+
23+
Create a client manually:
24+
25+
```csharp
2926
var client = new TermiiClient(new TermiiOptions
3027
{
3128
ApiKey = "your-termii-api-key"
3229
});
3330
```
3431

35-
The default base URL is `https://api.ng.termii.com`.
32+
The default base URL is `https://api.ng.termii.com`. Override it only when your Termii account or environment requires a different base URL:
33+
34+
```csharp
35+
var client = new TermiiClient(new TermiiOptions
36+
{
37+
ApiKey = "your-termii-api-key",
38+
BaseUrl = new Uri("https://api.ng.termii.com"),
39+
Timeout = TimeSpan.FromSeconds(30)
40+
});
41+
```
3642

37-
For ASP.NET Core applications, register the SDK with dependency injection:
43+
For ASP.NET Core applications, register `TermiiClient` with dependency injection:
3844

3945
```csharp
4046
builder.Services.AddTermii(options =>
@@ -43,7 +49,35 @@ builder.Services.AddTermii(options =>
4349
});
4450
```
4551

46-
Send a message:
52+
Use it from a service or endpoint:
53+
54+
```csharp
55+
public sealed class NotificationService
56+
{
57+
private readonly TermiiClient _termii;
58+
59+
public NotificationService(TermiiClient termii)
60+
{
61+
_termii = termii;
62+
}
63+
64+
public Task<TermiiMessageResponse> SendAsync(string phoneNumber, string message)
65+
{
66+
return _termii.Messaging.SendAsync(new SendMessageRequest
67+
{
68+
To = phoneNumber,
69+
From = "Termii",
70+
Sms = message,
71+
Channel = TermiiMessageChannel.Generic,
72+
Type = TermiiMessageType.Plain
73+
});
74+
}
75+
}
76+
```
77+
78+
## Messaging
79+
80+
Send a single SMS:
4781

4882
```csharp
4983
var response = await client.Messaging.SendAsync(new SendMessageRequest
@@ -56,22 +90,50 @@ var response = await client.Messaging.SendAsync(new SendMessageRequest
5690
});
5791
```
5892

59-
Fetch sender IDs:
93+
Send a bulk SMS:
6094

6195
```csharp
62-
var senderIds = await client.SenderIds.GetAsync();
96+
var response = await client.Messaging.SendBulkAsync(new SendBulkMessageRequest
97+
{
98+
To = new[] { "2348012345678", "2348098765432" },
99+
From = "Termii",
100+
Sms = "Hello everyone",
101+
Channel = TermiiMessageChannel.Generic,
102+
Type = TermiiMessageType.Plain
103+
});
63104
```
64105

65106
Send through the Number API:
66107

67108
```csharp
68-
var numberMessage = await client.Numbers.SendAsync(new SendNumberMessageRequest
109+
var response = await client.Numbers.SendAsync(new SendNumberMessageRequest
69110
{
70111
To = "2348012345678",
71112
Sms = "Hello from a dedicated Termii number"
72113
});
73114
```
74115

116+
## Sender IDs
117+
118+
Fetch sender IDs:
119+
120+
```csharp
121+
var senderIds = await client.SenderIds.GetAsync();
122+
```
123+
124+
Request a sender ID:
125+
126+
```csharp
127+
var request = await client.SenderIds.RequestAsync(new RequestSenderIdRequest
128+
{
129+
SenderId = "Termii",
130+
UseCase = "Transactional alerts",
131+
Company = "Example Ltd"
132+
});
133+
```
134+
135+
## Tokens and OTP
136+
75137
Send an OTP token:
76138

77139
```csharp
@@ -88,12 +150,115 @@ var token = await client.Tokens.SendAsync(new SendTokenRequest
88150
});
89151
```
90152

153+
Verify an OTP token:
154+
155+
```csharp
156+
var verification = await client.Tokens.VerifyAsync(new VerifyTokenRequest
157+
{
158+
PinId = token.PinId!,
159+
Pin = "123456"
160+
});
161+
```
162+
163+
Generate an in-app OTP:
164+
165+
```csharp
166+
var generated = await client.Tokens.GenerateAsync(new GenerateTokenRequest
167+
{
168+
PhoneNumber = "2348012345678",
169+
PinType = TermiiTokenPinType.Numeric,
170+
PinAttempts = 3,
171+
PinTimeToLive = 10,
172+
PinLength = 6
173+
});
174+
```
175+
176+
The token client also supports voice OTP, voice call OTP, email OTP, and WhatsApp OTP through `SendVoiceAsync`, `CallAsync`, `SendEmailAsync`, and `SendWhatsAppAsync`.
177+
178+
## Insights
179+
91180
Check account balance:
92181

93182
```csharp
94183
var balance = await client.Insights.GetBalanceAsync();
95184
```
96185

186+
Check DND status:
187+
188+
```csharp
189+
var dnd = await client.Insights.CheckDndAsync(new CheckDndRequest
190+
{
191+
PhoneNumber = "2348012345678"
192+
});
193+
```
194+
195+
Query number intelligence:
196+
197+
```csharp
198+
var number = await client.Insights.QueryNumberAsync(new QueryNumberRequest
199+
{
200+
PhoneNumber = "2348012345678",
201+
CountryCode = "NG"
202+
});
203+
```
204+
205+
Fetch message history:
206+
207+
```csharp
208+
var history = await client.Insights.GetMessageHistoryAsync(new GetMessageHistoryRequest
209+
{
210+
Page = 0,
211+
Size = 15,
212+
Sender = "Termii",
213+
Receiver = "2348012345678"
214+
});
215+
```
216+
217+
## Error Handling
218+
219+
The SDK throws `TermiiApiException` for non-success HTTP responses from Termii:
220+
221+
```csharp
222+
try
223+
{
224+
await client.Messaging.SendAsync(new SendMessageRequest
225+
{
226+
To = "2348012345678",
227+
From = "Termii",
228+
Sms = "Hello from .NET",
229+
Channel = TermiiMessageChannel.Generic
230+
});
231+
}
232+
catch (TermiiApiException ex)
233+
{
234+
Console.WriteLine($"HTTP status: {(int)ex.StatusCode}");
235+
Console.WriteLine($"Termii code: {ex.TermiiCode}");
236+
Console.WriteLine($"Termii message: {ex.TermiiMessage}");
237+
}
238+
```
239+
240+
Request models also validate required fields before sending. Missing required values throw `ArgumentException`, and invalid numeric ranges throw `ArgumentOutOfRangeException`.
241+
242+
## Supported APIs
243+
244+
Implemented in the current SDK:
245+
246+
- Messaging: send single, WhatsApp conversational, and bulk messages.
247+
- Sender IDs: list and request sender IDs.
248+
- Number API: send message through a dedicated Termii number.
249+
- Tokens: send, verify, generate, voice, email, and WhatsApp OTP flows.
250+
- Insights: balance, DND status, number intelligence, and message history.
251+
252+
Deferred or not yet implemented:
253+
254+
- WhatsApp template/device message APIs.
255+
- Campaign phonebook APIs.
256+
- Product notification email APIs.
257+
- Webhook event models.
258+
- `/api/sms/history/analytics`, pending additional verification.
259+
260+
See [docs/API_COVERAGE.md](docs/API_COVERAGE.md) for the detailed coverage matrix.
261+
97262
## Examples
98263

99264
Run the examples project after setting your Termii API key:
@@ -103,10 +268,17 @@ export TERMII_API_KEY="your-termii-api-key"
103268
dotnet run --project examples/Termii.Examples
104269
```
105270

106-
To send a live Number API example message, opt in explicitly:
271+
Run a live balance example:
107272

108273
```bash
109-
export TERMII_SEND_NUMBER_MESSAGE="true"
274+
export TERMII_EXAMPLE_ACTION="balance"
275+
dotnet run --project examples/Termii.Examples
276+
```
277+
278+
Send a live Number API example message:
279+
280+
```bash
281+
export TERMII_EXAMPLE_ACTION="number-message"
110282
export TERMII_EXAMPLE_PHONE_NUMBER="2348012345678"
111283
dotnet run --project examples/Termii.Examples
112284
```
@@ -128,20 +300,15 @@ export TERMII_TEST_PHONE_NUMBER="2348012345678"
128300
dotnet test tests/Termii.IntegrationTests
129301
```
130302

131-
As endpoint support is added, integration tests should remain credential-gated so normal CI can run without network access or Termii credits.
132-
133-
## Roadmap
303+
## Release
134304

135-
Development is tracked through GitHub issues:
305+
Releases are published from version tags through GitHub Actions. See [docs/RELEASING.md](docs/RELEASING.md) for the checklist.
136306

137-
- SDK foundation and HTTP pipeline.
138-
- Messaging APIs.
139-
- Sender ID and number APIs.
140-
- Token/OTP APIs.
141-
- Insights APIs.
142-
- CI, documentation, NuGet packaging, and GitHub Releases.
307+
## Links
143308

144-
Official Termii documentation: https://developer.termii.com/
309+
- Termii developer docs: https://developer.termii.com/
310+
- NuGet package: https://www.nuget.org/packages/Termii.SDK
311+
- API coverage: [docs/API_COVERAGE.md](docs/API_COVERAGE.md)
145312

146313
## License
147314

docs/API_COVERAGE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ The Termii docs describe a REST/JSON API and state that each account has its own
3131
| API coverage matrix | Implemented | #14, PR #18 |
3232
| Error handling and request validation | Implemented | #6, PR #19 |
3333
| Reusable fake HTTP test helpers and opt-in live test conventions | Implemented | #10, PR #20 |
34-
| README endpoint examples | Planned | #12 |
34+
| README endpoint examples | In progress | #12 |
3535
| CI build/test/package validation | Implemented | #11, PR #22 |
3636
| NuGet package metadata and publishing workflow | Implemented | #8, PR #23 |
37-
| First GitHub Release and NuGet publish | Planned | #13 |
37+
| First GitHub Release and NuGet publish | Implemented | #13, PR #28, v0.2.0 |
3838

3939
## Endpoint Coverage
4040

@@ -53,10 +53,10 @@ The Termii docs describe a REST/JSON API and state that each account has its own
5353
| Token | Send voice call token | POST | `/api/sms/otp/call` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
5454
| Token | Send email OTP token | POST | `/api/email/otp/send` | JSON body | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
5555
| Token | Send WhatsApp OTP token | POST | `/api/sms/send` | JSON body with `channel=whatsapp_otp` | `TermiiClient.Tokens` | Implemented | #4, PR #26 | Unit tests added |
56-
| Insights | Get balance | GET | `/api/get-balance` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
57-
| Insights | Search DND/number status | GET | `/api/check/dnd` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
58-
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
59-
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | In progress | #9 | Unit tests added |
56+
| Insights | Get balance | GET | `/api/get-balance` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
57+
| Insights | Search DND/number status | GET | `/api/check/dnd` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
58+
| Insights | Query number intelligence/status | GET | `/api/insight/number/query` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
59+
| Insights | Fetch message inbox/history | GET | `/api/sms/inbox` | Query | `TermiiClient.Insights` | Implemented | #9, PR #27 | Unit tests added |
6060
| Insights | Fetch message analytics/history | GET | `/api/sms/history/analytics` | Query | `TermiiClient.Insights` | Needs verification | #9 | Planned unit + optional integration |
6161

6262
## Deferred Coverage

examples/Termii.Examples/Program.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,33 @@
1515

1616
Console.WriteLine($"Termii client configured for {client.Options.BaseUrl}.");
1717

18+
var action = Environment.GetEnvironmentVariable("TERMII_EXAMPLE_ACTION");
1819
var phoneNumber = Environment.GetEnvironmentVariable("TERMII_EXAMPLE_PHONE_NUMBER");
19-
var sendNumberMessage = string.Equals(
20-
Environment.GetEnvironmentVariable("TERMII_SEND_NUMBER_MESSAGE"),
21-
"true",
22-
StringComparison.OrdinalIgnoreCase);
2320

24-
if (!sendNumberMessage || string.IsNullOrWhiteSpace(phoneNumber))
21+
if (string.Equals(action, "balance", StringComparison.OrdinalIgnoreCase))
2522
{
26-
Console.WriteLine("Set TERMII_SEND_NUMBER_MESSAGE=true and TERMII_EXAMPLE_PHONE_NUMBER to send a Number API message.");
23+
var balance = await client.Insights.GetBalanceAsync();
24+
25+
Console.WriteLine($"Balance: {balance.Balance} {balance.Currency}".Trim());
2726
return;
2827
}
2928

30-
var response = await client.Numbers.SendAsync(new SendNumberMessageRequest
29+
if (string.Equals(action, "number-message", StringComparison.OrdinalIgnoreCase))
3130
{
32-
To = phoneNumber,
33-
Sms = "Hello from Termii.SDK",
34-
});
31+
if (string.IsNullOrWhiteSpace(phoneNumber))
32+
{
33+
Console.WriteLine("Set TERMII_EXAMPLE_PHONE_NUMBER to send a Number API message.");
34+
return;
35+
}
36+
37+
var response = await client.Numbers.SendAsync(new SendNumberMessageRequest
38+
{
39+
To = phoneNumber,
40+
Sms = "Hello from Termii.SDK",
41+
});
42+
43+
Console.WriteLine($"Number API response: {response.Message ?? response.Code ?? "sent"}");
44+
return;
45+
}
3546

36-
Console.WriteLine($"Number API response: {response.Message ?? response.Code ?? "sent"}");
47+
Console.WriteLine("Set TERMII_EXAMPLE_ACTION=balance or TERMII_EXAMPLE_ACTION=number-message to run a live example.");

0 commit comments

Comments
 (0)