Skip to main content

C# Expo Push Notification Client


public class PushDataPayload
{
public string? PushMessage { get; set; }
public object? PushData { get; set; }
}

var pushTicketResponse = await _expoPushApiClient.PushSendAsync(new PushTicketRequest
{
PushTo = chunkGroup.Select(p => p.PushToken).ToList(), // Send to all tokens for the same user
PushTitle = pushTitle,
PushBody = pushPayload.PushMessage,
PushData = pushPayload.PushData,
PushBadgeCount = badgeCount
});

ExpoPushApiClient.cs
using System.Text;
using Newtonsoft.Json;
using Serilog;

public class ExpoPushApiClient
{
private const string _pushSendPath = "/--/api/v2/push/send";
private const string _pushGetReceiptsPath = "/--/api/v2/push/getReceipts";
private readonly HttpClient _httpClient;
private readonly ILogger _logger;

public ExpoPushApiClient(IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("Expo");
_logger = Log.ForContext<ExpoPushApiClient>();
}

public async Task<PushTicketResponse> PushSendAsync(PushTicketRequest pushTicketRequest)
{
var ticketResponse = await PostAsync<PushTicketRequest, PushTicketResponse>(pushTicketRequest, _pushSendPath);
return ticketResponse;
}

public async Task<PushResceiptResponse> PushGetReceiptsAsync(PushReceiptRequest pushReceiptRequest)
{
var receiptResponse =
await PostAsync<PushReceiptRequest, PushResceiptResponse>(pushReceiptRequest, _pushGetReceiptsPath);
return receiptResponse;
}

private async Task<U> PostAsync<T, U>(T requestObj, string path) where T : new()
{
var serializedRequestObj = JsonConvert.SerializeObject(requestObj);
var requestBody = new StringContent(serializedRequestObj, Encoding.UTF8, "application/json");
var responseBody = default(U);
var response = await _httpClient.PostAsync(path, requestBody);

if (response.IsSuccessStatusCode)
{
var rawResponseBody = await response.Content.ReadAsStringAsync();
responseBody = JsonConvert.DeserializeObject<U>(rawResponseBody);
}
else
{
var rawResponseBody = await response.Content.ReadAsStringAsync();
_logger.Error("Error sending push notification: {RawResponseBody}", rawResponseBody);

responseBody = JsonConvert.DeserializeObject<U>(rawResponseBody);
}

return responseBody;
}
}

[JsonObject(MemberSerialization.OptIn)]
public class PushReceiptErrorInformation
{
[JsonProperty(PropertyName = "code")]
public string ErrorCode { get; set; }

[JsonProperty(PropertyName = "message")]
public string ErrorMessage { get; set; }
}

[JsonObject(MemberSerialization.OptIn)]
public class PushReceiptRequest
{

[JsonProperty(PropertyName ="ids")]
public List<string> PushTicketIds { get; set; }
}

[JsonObject(MemberSerialization.OptIn)]
public class PushResceiptResponse
{
[JsonProperty(PropertyName = "data")]
public Dictionary<string,PushTicketDeliveryStatus> PushTicketReceipts { get; set; }

[JsonProperty(PropertyName = "errors")]
public List<PushReceiptErrorInformation> ErrorInformations { get; set; }
}

[JsonObject(MemberSerialization.OptIn)]
public class PushTicketDeliveryStatus
{
[JsonProperty(PropertyName = "status")]
public string DeliveryStatus { get; set; }

[JsonProperty(PropertyName = "message")]
public string DeliveryMessage { get; set; }

[JsonProperty(PropertyName = "details")]
public object DeliveryDetails { get; set; }
}

[JsonObject (MemberSerialization.OptIn)]
public class PushTicketErrors {
[JsonProperty (PropertyName = "code")]
public string ErrorCode { get; set; }

[JsonProperty (PropertyName = "message")]
public string ErrorMessage { get; set; }
}


[JsonObject(MemberSerialization.OptIn)]
public class PushTicketRequest
{
[JsonProperty(PropertyName = "to")]
public List<string> PushTo { get; set; }

[JsonProperty(PropertyName = "data")]
public object PushData { get; set; }

[JsonProperty(PropertyName = "title")]
public string PushTitle { get; set; }

[JsonProperty(PropertyName = "body")]
public string PushBody { get; set; }

[JsonProperty(PropertyName = "ttl")]
public int? PushTTL { get; set; }

[JsonProperty(PropertyName = "expiration")]
public int? PushExpiration { get; set; }

[JsonProperty(PropertyName = "priority")] //'default' | 'normal' | 'high'
public string PushPriority { get; set; }

[JsonProperty(PropertyName = "subtitle")]
public string PushSubTitle { get; set; }

[JsonProperty(PropertyName = "sound")] //'default' | null
public string PushSound { get; set; }

[JsonProperty(PropertyName = "badge")]
public int? PushBadgeCount { get; set; }

[JsonProperty(PropertyName = "channelId")]
public string PushChannelId { get; set; }
}

[JsonObject (MemberSerialization.OptIn)]
public class PushTicketResponse {
[JsonProperty (PropertyName = "data")]
public List<PushTicketStatus> PushTicketStatuses { get; set; }

[JsonProperty (PropertyName = "errors")]
public List<PushTicketErrors> PushTicketErrors { get; set; }
}

[JsonObject (MemberSerialization.OptIn)]
public class PushTicketStatus {

[JsonProperty (PropertyName = "status")] //"error" | "ok",
public string TicketStatus { get; set; }

[JsonProperty (PropertyName = "id")]
public string TicketId { get; set; }

[JsonProperty (PropertyName = "message")]
public string TicketMessage { get; set; }

[JsonProperty (PropertyName = "details")]
public object TicketDetails { get; set; }
}