diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9a1c81b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -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
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb36e42
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+bin/
+obj/
+.vs/
+.vscode/
+.idea/
+TestResults/
+coverage/
+*.user
+*.suo
+*.nupkg
+*.snupkg
+artifacts/
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..bdc37b5
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+ true
+ enable
+ enable
+ latest
+ https://github.com/teesofttech/Termii.SDK
+ git
+ https://github.com/teesofttech/Termii.SDK
+ MIT
+ Teesoft Tech
+ Teesoft Tech
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3e02b29
--- /dev/null
+++ b/README.md
@@ -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/
diff --git a/Termii.SDK.sln b/Termii.SDK.sln
new file mode 100644
index 0000000..ad4a40f
--- /dev/null
+++ b/Termii.SDK.sln
@@ -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
diff --git a/docs/API_COVERAGE.md b/docs/API_COVERAGE.md
new file mode 100644
index 0000000..44234db
--- /dev/null
+++ b/docs/API_COVERAGE.md
@@ -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
diff --git a/examples/Termii.Examples/Program.cs b/examples/Termii.Examples/Program.cs
new file mode 100644
index 0000000..31b4108
--- /dev/null
+++ b/examples/Termii.Examples/Program.cs
@@ -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}.");
diff --git a/examples/Termii.Examples/Termii.Examples.csproj b/examples/Termii.Examples/Termii.Examples.csproj
new file mode 100644
index 0000000..387485f
--- /dev/null
+++ b/examples/Termii.Examples/Termii.Examples.csproj
@@ -0,0 +1,11 @@
+
+
+ Exe
+ net8.0
+ false
+
+
+
+
+
+
diff --git a/src/Termii/Termii.csproj b/src/Termii/Termii.csproj
new file mode 100644
index 0000000..cc7e907
--- /dev/null
+++ b/src/Termii/Termii.csproj
@@ -0,0 +1,12 @@
+
+
+ netstandard2.0;net8.0
+ Termii
+ Termii
+ Termii
+ A .NET SDK for the Termii messaging, token, and insights APIs.
+ termii;sms;otp;messaging;dotnet;sdk
+ true
+ $(NoWarn);1591
+
+
diff --git a/src/Termii/TermiiClient.cs b/src/Termii/TermiiClient.cs
new file mode 100644
index 0000000..dfe9b6f
--- /dev/null
+++ b/src/Termii/TermiiClient.cs
@@ -0,0 +1,15 @@
+namespace Termii;
+
+///
+/// Main entry point for the Termii SDK.
+///
+public sealed class TermiiClient
+{
+ public TermiiClient(TermiiOptions options)
+ {
+ Options = options ?? throw new ArgumentNullException(nameof(options));
+ Options.Validate();
+ }
+
+ public TermiiOptions Options { get; }
+}
diff --git a/src/Termii/TermiiOptions.cs b/src/Termii/TermiiOptions.cs
new file mode 100644
index 0000000..6e3ea21
--- /dev/null
+++ b/src/Termii/TermiiOptions.cs
@@ -0,0 +1,26 @@
+namespace Termii;
+
+///
+/// Configuration required to call the Termii API.
+///
+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.");
+ }
+ }
+}
diff --git a/tests/Termii.IntegrationTests/Termii.IntegrationTests.csproj b/tests/Termii.IntegrationTests/Termii.IntegrationTests.csproj
new file mode 100644
index 0000000..219cfb4
--- /dev/null
+++ b/tests/Termii.IntegrationTests/Termii.IntegrationTests.csproj
@@ -0,0 +1,19 @@
+
+
+ net8.0
+ false
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/tests/Termii.IntegrationTests/TermiiClientIntegrationTests.cs b/tests/Termii.IntegrationTests/TermiiClientIntegrationTests.cs
new file mode 100644
index 0000000..df2c1d8
--- /dev/null
+++ b/tests/Termii.IntegrationTests/TermiiClientIntegrationTests.cs
@@ -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);
+ }
+}
diff --git a/tests/Termii.Tests/Termii.Tests.csproj b/tests/Termii.Tests/Termii.Tests.csproj
new file mode 100644
index 0000000..219cfb4
--- /dev/null
+++ b/tests/Termii.Tests/Termii.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+ net8.0
+ false
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/tests/Termii.Tests/TermiiClientTests.cs b/tests/Termii.Tests/TermiiClientTests.cs
new file mode 100644
index 0000000..46efaab
--- /dev/null
+++ b/tests/Termii.Tests/TermiiClientTests.cs
@@ -0,0 +1,29 @@
+using Termii;
+using Xunit;
+
+namespace Termii.Tests;
+
+public sealed class TermiiClientTests
+{
+ [Fact]
+ public void ConstructorRejectsMissingApiKey()
+ {
+ var exception = Assert.Throws(() => new TermiiClient(new TermiiOptions()));
+
+ Assert.Contains("API key", exception.Message, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void ConstructorAcceptsValidOptions()
+ {
+ var options = new TermiiOptions
+ {
+ ApiKey = "test-api-key",
+ };
+
+ var client = new TermiiClient(options);
+
+ Assert.Equal("test-api-key", client.Options.ApiKey);
+ Assert.Equal(TermiiOptions.DefaultBaseUrl, client.Options.BaseUrl.ToString().TrimEnd('/'));
+ }
+}