ExpoNotifyClient 1.0.0

dotnet add package ExpoNotifyClient --version 1.0.0
                    
NuGet\Install-Package ExpoNotifyClient -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ExpoNotifyClient" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ExpoNotifyClient" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="ExpoNotifyClient" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ExpoNotifyClient --version 1.0.0
                    
#r "nuget: ExpoNotifyClient, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ExpoNotifyClient@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ExpoNotifyClient&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=ExpoNotifyClient&version=1.0.0
                    
Install as a Cake Tool

ExpoNotifyClient

.NET client library for sending push notifications via the Expo Push Service. Supports single and batch push notifications, delivery receipt retrieval, automatic batching, exponential backoff retry, and gzip compression.

Send push notifications from .NET through Expo's Push Service.

Install

dotnet add package ExpoNotifyClient

Quick start

using ExpoNotifyClient;
using ExpoNotifyClient.Models;

var client = new ExpoClient();

var message = new ExpoPushMessage
{
    to = "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
    title = "Hello from .NET",
    body = "This is a push notification."
};

ExpoPushResponse response = await client.SendAsync(message);

if (response.IsSuccess)
    Console.WriteLine($"Sent! Ticket: {response.TicketIds.First()}");
else
    foreach (var err in response.AllErrorMessages)
        Console.WriteLine($"Error: {err}");

That's it. One message, one response.

Sending in bulk (many users)

If you have more than one message, you can send them in a batch. Expo accepts up to 100 at a time in 1 single request. Faq

var messages = new List<ExpoPushMessage> { msg1, msg2, msg3 };
ExpoPushResponse response = await client.SendBatchAsync(messages);

If you have a lot — hundreds or thousands — use SendBulkAsync. It splits them into batches of 100 automatically.

var results = await client.SendBulkAsync(hugeListOfMessages);

Checking delivery

Send gives you tickets. Tickets are receipts-to-be. You can check if a notification actually arrived.

ExpoReceiptResponse receipt = await client.GetReceiptAsync(ticketId);

Or check a bunch at once.

ExpoReceiptResponse receipts = await client.GetReceiptsAsync(listOfTicketIds);

Validation

You can check a message before sending it. Catches bad tokens, titles that are too long, body over 4000 chars, oversized data payloads, TTL that's too high.

ValidationResult result = client.ValidateMessage(message);

if (!result.IsValid)
    Console.WriteLine(result.ErrorMessage);

ValidateMessages works on a list and tells you which index has the problem.

Configuration

Three ways to create a client:

// Defaults
var client = new ExpoClient();

// Custom options
var client = new ExpoClient(options);

// For DI / IHttpClientFactory
var client = new ExpoClient(httpClient, options);

Options:

var options = new ExpoClientOptions
{
    AccessToken = "your-token",          // Required if push security is on
    TimeoutSeconds = 15,                  // Default 30
    MaxRetryAttempts = 5,                 // Default 3
    EnableCompression = true              // Default true
};
Property Default What it does
BaseUrl https://exp.host/--/api/v2 Expo API endpoint
AccessToken null Bearer token
TimeoutSeconds 30 HTTP timeout
MaxRetryAttempts 3 Retries on 429, 5xx, timeouts
InitialBackoffMs 1000 Starting retry delay
MaxBackoffMs 30000 Max retry delay
EnableCompression true Gzip/deflate
MaxNotificationsPerBatch 100 Max per request

Errors

Bad requests, bad tokens, unauthorized — those throw ExpoApiException right away.

try
{
    var response = await client.SendAsync(message);
}
catch (ExpoApiException ex)
{
    Console.WriteLine($"{(int)ex.StatusCode}: {ex.Message}");
}

Transient failures — 429, 5xx, timeouts, network blips — get retried automatically with exponential backoff. The library handles those. You don't have to do anything.

Sometimes the HTTP request itself succeeds but individual messages in the batch fail. Check response.FailedTickets or response.AllErrorMessages.

foreach (var ticket in response.FailedTickets)
    Console.WriteLine($"{ticket.Message} — {ticket.Details?.Error}");

Common error codes from Expo:

Code What it means
DeviceNotRegistered Remove this token from your database
MessageTooBig Payload is too large
MessageRateExceeded You're sending too fast
InvalidCredentials Access token is wrong
TooManyRequests You're being rate-limited

Message fields

var msg = new ExpoPushMessage
{
    to = "ExponentPushToken[...]",   // required
    title = "Hi",                     // max 200
    body = "This is the body",        // max 4000
    data = new { screen = "home" },   // custom JSON, max 4KB
    ttl = 3600,                       // seconds, max 28 days
    priority = "high",                // default, normal, high
    sound = "default",                // iOS
    badge = 1,                        // iOS
    subtitle = "sub",                 // iOS
    interruptionLevel = "critical",   // iOS
    mutableContent = true,            // iOS
    channelId = "updates",            // android
    icon = "notification_icon",       // android
    richContent = new ExpoRichContent { image = "https://..." },
    categoryId = "new_message",
    collapseId = "thread-1",
    tag = "order-123",                // android, replaces existing
};

If you want strongly-typed data:

var msg = new ExpoPushMessage<MyData> { Data = new MyData { ... } };

Responses

ExpoPushResponse has some helpers:

response.IsSuccess       // all tickets were OK
response.HasErrors       // any errors at all
response.TicketIds       // IDs of successful tickets
response.FailedTickets   // tickets that failed
response.AllErrorMessages // everything that went wrong

ExpoReceiptResponse:

receipt.IsSuccess         // all receipts OK
receipt.HasErrors         // any errors
receipt.FailedReceiptIds  // IDs with errors
receipt.Data[ticketId]    // look up a specific receipt

License

This project is licensed under the MIT License - see the LICENSE file for details.

Building

dotnet build src/ExpoNotifyClient
dotnet test tests/UnitTests
dotnet pack src/ExpoNotifyClient -c Release
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 100 7/13/2026