DEV Community

Cover image for How I Built and Published a .NET NuGet Package for the Giant SMS API
Benjamin Aduo
Benjamin Aduo

Posted on

How I Built and Published a .NET NuGet Package for the Giant SMS API

A while back, I needed to integrate SMS into a .NET project. Giant SMS had a REST API, but no official .NET client. The only existing library was a PHP one from 6–7 years ago, and it only covered two methods: send and getBalance.

So I built my own. It now has nearly 2,000 downloads on NuGet. Here's exactly how I did it.

The Problem

Wiring up raw HTTP calls to the Giant SMS API in every project gets repetitive fast:

  • Manually setting Authorization headers
  • Remembering which endpoints use token auth vs. username/password
  • Deserializing responses every time
  • Scattering credentials across your codebase

I wanted something that felt native to .NET. Configure once in appsettings.json,
register with DI, and just call a method.

Designing the Public API

The first decision was the interface. I wanted consumers to never touch HttpClient
directly, and I wanted methods that mapped clearly to what the API actually does:

public interface IGiantSmsService
{
    bool IsReady { get; }
    Task<SingleSmsResponse> SendSingleMessage(string to, string msg);
    Task<SingleSmsResponse> SendMessageWithToken(SingleMessageRequest messageRequest);
    Task<BaseResponse> SendBulkMessages(BulkMessageRequest messageRequest);
    Task<SingleSmsResponse> CheckMessageStatus(string messageId);
    Task<BaseResponse> GetBalance();
    Task<SenderIdResponse> GetSenderIds();
    Task<BaseResponse> RegisterSenderId(RegisterSenderIdRequest senderIdRequest);
}
Enter fullscreen mode Exit fullscreen mode

Seven methods, the full surface of the API, no more, no less.

The IsReady property is a small but useful addition. It lets consumers do a quick sanity check at startup rather than discovering a missing token on the first SMS send:

csharp _isReady = !string.IsNullOrWhiteSpace(_connection.Token) &&
!string.IsNullOrWhiteSpace(_connection.Username);

Handling Two Auth Methods

This was the most interesting design challenge. The Giant SMS API uses two different
authentication schemes depending on the endpoint:

  • Token-based (Basic Authorization header) — for sending messages and checking status

  • Username/password (query parameters) — for balance, sender IDs, and registration

The library handles both transparently. Consumers never have to think about which auth method an endpoint needs:

// Token auth — Authorization header
request.Headers.Add("Authorization", $"Basic {token}");
Enter fullscreen mode Exit fullscreen mode
// Username/password auth — query string
await _httpClient.GetAsync(
    $"{BaseEndpointUrl}/balance?username={username}&password={password}");
Enter fullscreen mode Exit fullscreen mode

All credentials live in a single configuration object:

public class GiantSmsConnection
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string Token { get; set; }
    public string SenderId { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Configuration via appsettings.json

Add this to your appsettings.json:

"GiantSmsConnection": {
  "Username": "your_username",
  "Password": "your_password",
  "Token": "your_token",
  "SenderId": "YourApp"
}
Enter fullscreen mode Exit fullscreen mode

The library binds to it automatically using the IOptions<T> pattern , no manual
mapping needed.

DI Registration in One Line

builder.Services.AddGiantSms(builder.Configuration);
Enter fullscreen mode Exit fullscreen mode

Under the hood:

public static void AddGiantSms(this IServiceCollection services, IConfiguration configuration)
{
    services.AddHttpClient();
    services.Configure<GiantSmsConnection>(options =>
        configuration.GetSection(nameof(GiantSmsConnection)).Bind(options));
    services.AddScoped<IGiantSmsService, GiantSmsService>();
}
Enter fullscreen mode Exit fullscreen mode

IHttpClientFactory is used instead of newing up HttpClient directly. This avoids socket exhaustion in long-running applications, a common footgun in .NET.

Then inject and use anywhere:

public class NotificationService
{
    private readonly IGiantSmsService _sms;

    public NotificationService(IGiantSmsService sms) => _sms = sms;

    public async Task Notify(string phone, string message)
    {
        if (!_sms.IsReady) return;
        await _sms.SendSingleMessage(phone, message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Packaging for NuGet

The .csproj is where you define your package identity:

<PackageId>GiantSms.Net</PackageId>
<Version>2.0.0</Version>
<Authors>Benjamin Aduo</Authors>
<Description>A simple .NET library for sending SMS via the Giant SMS API.</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<RepositoryUrl>https://github.com/benaduo/GiantSms.Net</RepositoryUrl>
Enter fullscreen mode Exit fullscreen mode

A few things worth noting:

snupkg publishes debug symbols separately so consumers can step into your library during debugging
GeneratePackageOnBuild produces a .nupkg on every build, this is convenient during development
Always include a RepositoryUrl, it shows on the NuGet page and builds trust

Publishing is one command once you have an API key from nuget.org:

dotnet nuget push GiantSms.Net.2.0.0.nupkg --api-key YOUR_KEY --source https://api.nuget.org/v3/index.json

The Result

The package is live at nuget.org/packages/GiantSms.Net with nearly 2,000 downloads. Not millions but knowing real developers are using something you built from scratch is a good feeling.

dotnet add package GiantSms.Net

Full source on GitHub. PRs and issues welcome.

Top comments (0)