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
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
pull_request:
push:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Restore
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore

- name: Test
run: dotnet test --configuration Release --no-build
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bin/
obj/
.vs/
.vscode/
.idea/
TestResults/
coverage/
*.user
*.suo
*.nupkg
*.snupkg
artifacts/
14 changes: 14 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<RepositoryUrl>https://github.com/teesofttech/Termii.SDK</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/teesofttech/Termii.SDK</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Authors>Teesoft Tech</Authors>
<Company>Teesoft Tech</Company>
</PropertyGroup>
</Project>
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Termii .NET SDK

A .NET SDK for the Termii messaging, token, and insights APIs.

> 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.

## Current Status

The initial repository setup includes:

- A `Termii` SDK project targeting `netstandard2.0` and `net8.0`.
- A lightweight unit test project.
- An examples project for developer-facing usage.
- An API coverage matrix in `docs/API_COVERAGE.md`.

## Usage

```csharp
using Termii;

var client = new TermiiClient(new TermiiOptions
{
ApiKey = "your-termii-api-key"
});
```

The default base URL is `https://api.ng.termii.com`.

## Examples

Run the examples project after setting your Termii API key:

```bash
export TERMII_API_KEY="your-termii-api-key"
dotnet run --project examples/Termii.Examples
```

## Tests

Run the local test suite:

```bash
dotnet test
```

Integration tests are opt-in and must not call live Termii endpoints unless the required environment variables are present:

```bash
export TERMII_API_KEY="your-termii-api-key"
export TERMII_BASE_URL="https://api.ng.termii.com"
dotnet test tests/Termii.IntegrationTests
```

As endpoint support is added, integration tests should remain credential-gated so normal CI can run without network access or Termii credits.

## Roadmap

Development is tracked through GitHub issues:

- SDK foundation and HTTP pipeline.
- Messaging APIs.
- Sender ID and number APIs.
- Token/OTP APIs.
- Insights APIs.
- CI, documentation, NuGet packaging, and GitHub Releases.

Official Termii documentation: https://developer.termii.com/
88 changes: 88 additions & 0 deletions Termii.SDK.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Termii", "src\Termii\Termii.csproj", "{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Termii.Tests", "tests\Termii.Tests\Termii.Tests.csproj", "{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Termii.IntegrationTests", "tests\Termii.IntegrationTests\Termii.IntegrationTests.csproj", "{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Termii.Examples", "examples\Termii.Examples\Termii.Examples.csproj", "{063DC3E1-23F8-4D66-918D-8FB1C61B648D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|x64.Build.0 = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Debug|x86.Build.0 = Debug|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|Any CPU.Build.0 = Release|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|x64.ActiveCfg = Release|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|x64.Build.0 = Release|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|x86.ActiveCfg = Release|Any CPU
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5}.Release|x86.Build.0 = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|x64.ActiveCfg = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|x64.Build.0 = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|x86.ActiveCfg = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Debug|x86.Build.0 = Debug|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|Any CPU.Build.0 = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|x64.ActiveCfg = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|x64.Build.0 = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|x86.ActiveCfg = Release|Any CPU
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48}.Release|x86.Build.0 = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|x64.ActiveCfg = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|x64.Build.0 = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|x86.ActiveCfg = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Debug|x86.Build.0 = Debug|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|Any CPU.Build.0 = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|x64.ActiveCfg = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|x64.Build.0 = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|x86.ActiveCfg = Release|Any CPU
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449}.Release|x86.Build.0 = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|x64.ActiveCfg = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|x64.Build.0 = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|x86.ActiveCfg = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Debug|x86.Build.0 = Debug|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|Any CPU.Build.0 = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|x64.ActiveCfg = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|x64.Build.0 = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|x86.ActiveCfg = Release|Any CPU
{063DC3E1-23F8-4D66-918D-8FB1C61B648D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2C2E40AF-504E-4A61-B46A-84A1BC352ED5} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{6AE7CBA2-1987-477E-B14D-C34F50A5DD48} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{73C4DC47-8035-4F23-8C46-1AAFE5E7D449} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{063DC3E1-23F8-4D66-918D-8FB1C61B648D} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
EndGlobalSection
EndGlobal
21 changes: 21 additions & 0 deletions docs/API_COVERAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Termii API Coverage

This matrix tracks the Termii API surface against SDK implementation work.

| Area | Capability | SDK status | Issue |
| --- | --- | --- | --- |
| Foundation | Solution, projects, examples, basic tests | In progress | #1 |
| Foundation | Client configuration, authentication, HTTP pipeline | Planned | #2 |
| Foundation | Error handling and validation | Planned | #6 |
| Messaging | Send SMS and channel messages | Planned | #7 |
| Messaging | Sender ID APIs | Planned | #3 |
| Messaging | Number API messaging | Planned | #5 |
| Token | Send, verify, and generate OTP | Planned | #4 |
| Insights | Balance, DND, number status, message history | Planned | #9 |
| Release | NuGet package metadata and publishing workflow | Planned | #8 |
| Release | GitHub Release and NuGet publication | Planned | #13 |

References:

- Termii developer docs: https://developer.termii.com/
- Postman collection: https://termii.s3.us-west-1.amazonaws.com/upload/files/UozvGXj5czYEeY4OmE2f.json
16 changes: 16 additions & 0 deletions examples/Termii.Examples/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Termii;

var apiKey = Environment.GetEnvironmentVariable("TERMII_API_KEY");

if (string.IsNullOrWhiteSpace(apiKey))
{
Console.WriteLine("Set TERMII_API_KEY to run the Termii examples.");
return;
}

var client = new TermiiClient(new TermiiOptions
{
ApiKey = apiKey,
});

Console.WriteLine($"Termii client configured for {client.Options.BaseUrl}.");
11 changes: 11 additions & 0 deletions examples/Termii.Examples/Termii.Examples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../../src/Termii/Termii.csproj" />
</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions src/Termii/Termii.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<AssemblyName>Termii</AssemblyName>
<RootNamespace>Termii</RootNamespace>
<PackageId>Termii</PackageId>
<Description>A .NET SDK for the Termii messaging, token, and insights APIs.</Description>
<PackageTags>termii;sms;otp;messaging;dotnet;sdk</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
</Project>
15 changes: 15 additions & 0 deletions src/Termii/TermiiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Termii;

/// <summary>
/// Main entry point for the Termii SDK.
/// </summary>
public sealed class TermiiClient
{
public TermiiClient(TermiiOptions options)
{
Options = options ?? throw new ArgumentNullException(nameof(options));
Options.Validate();
}

public TermiiOptions Options { get; }
}
26 changes: 26 additions & 0 deletions src/Termii/TermiiOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Termii;

/// <summary>
/// Configuration required to call the Termii API.
/// </summary>
public sealed class TermiiOptions
{
public const string DefaultBaseUrl = "https://api.ng.termii.com";

public string ApiKey { get; set; } = string.Empty;

public Uri BaseUrl { get; set; } = new(DefaultBaseUrl);

internal void Validate()
{
if (string.IsNullOrWhiteSpace(ApiKey))
{
throw new InvalidOperationException("A Termii API key is required.");
}

if (!BaseUrl.IsAbsoluteUri)
{
throw new InvalidOperationException("The Termii base URL must be absolute.");
}
}
}
19 changes: 19 additions & 0 deletions tests/Termii.IntegrationTests/Termii.IntegrationTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../src/Termii/Termii.csproj" />
</ItemGroup>
</Project>
34 changes: 34 additions & 0 deletions tests/Termii.IntegrationTests/TermiiClientIntegrationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Termii;
using Xunit;

namespace Termii.IntegrationTests;

public sealed class TermiiClientIntegrationTests
{
[Fact]
public void CanCreateClientFromIntegrationEnvironment()
{
var apiKey = Environment.GetEnvironmentVariable("TERMII_API_KEY");

if (string.IsNullOrWhiteSpace(apiKey))
{
return;
}

var baseUrl = Environment.GetEnvironmentVariable("TERMII_BASE_URL");
var options = new TermiiOptions
{
ApiKey = apiKey,
};

if (!string.IsNullOrWhiteSpace(baseUrl))
{
options.BaseUrl = new Uri(baseUrl);
}

var client = new TermiiClient(options);

Assert.False(string.IsNullOrWhiteSpace(client.Options.ApiKey));
Assert.True(client.Options.BaseUrl.IsAbsoluteUri);
}
}
19 changes: 19 additions & 0 deletions tests/Termii.Tests/Termii.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="../../src/Termii/Termii.csproj" />
</ItemGroup>
</Project>
Loading
Loading