expox

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2026 License: MIT Imports: 14 Imported by: 0

README

expo-server-sdk-go

Go Reference

Server-side Go SDK for Expo Push Notifications.

Features

  • Full Expo Push API coverage (send, broadcast, receipts)
  • Concurrent batch sending with configurable parallelism
  • Gzip compression (request & response, conditional on payload size)
  • 429 rate limit retry with Retry-After header support
  • Pluggable rate limiting: Noop (default), Local token bucket, Redis distributed
  • Payload validation (4KB limit)
  • Complete message field support (iOS, Android, shared)

Install

go get github.com/Trashwbin/expo-server-sdk-go

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    expox "github.com/Trashwbin/expo-server-sdk-go"
    "github.com/Trashwbin/expo-server-sdk-go/types"
)

func main() {
    client := expox.NewClient(
        expox.WithAccessToken("your-expo-access-token"),
    )

    result, err := client.SendSingle(context.Background(), expox.Message{
        To:    "ExponentPushToken[xxxxxxxxxxxxxxxxxxxxxx]",
        Title: "Hello",
        Body:  "World!",
        Sound: types.DefaultSound(),
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Success: %v, TicketID: %s\n", result.Success, result.TicketID)
}

Usage

Send to multiple recipients
messages := []expox.Message{
    {To: "ExponentPushToken[aaa]", Title: "Hi", Body: "Message 1"},
    {To: "ExponentPushToken[bbb]", Title: "Hi", Body: "Message 2"},
}

result, err := client.Send(context.Background(), messages)
// result.SuccessCount, result.FailureCount, result.Results
Broadcast (same content, many tokens)
tokens := []string{"ExponentPushToken[aaa]", "ExponentPushToken[bbb]", "ExponentPushToken[ccc]"}

result, err := client.SendBroadcast(context.Background(), tokens, expox.Message{
    Title: "Announcement",
    Body:  "Hello everyone!",
})
Check receipts
// Collect ticket IDs from send results, wait ~15 minutes, then:
receipts, err := client.GetReceipts(context.Background(), ticketIDs)
for id, receipt := range receipts {
    if receipt.IsDeviceNotRegistered() {
        // Remove this token from your database
    }
}

// Or use ProcessReceipts for structured results:
results, err := client.ProcessReceipts(context.Background(), ticketIDs)
for _, r := range results {
    if r.Failed && r.Error.IsTokenInvalid() {
        // Token should be removed
    }
}
Validate payload size
msg := expox.Message{To: token, Title: "Big", Body: largeContent}
if err := expox.ValidatePayload(msg); err != nil {
    // Payload exceeds 4KB
}
Token validation
expox.IsValidPushToken("ExponentPushToken[xxx]") // true
expox.IsValidPushToken("ExpoPushToken[xxx]")      // true
expox.IsValidPushToken("a1234567-89ab-cdef-0123-456789abcdef") // true (UUID)
expox.IsValidPushToken("invalid")                 // false

Rate Limiting

Three built-in modes:

import "github.com/Trashwbin/expo-server-sdk-go/limiter"

// 1. No limit (default)
client := expox.NewClient()

// 2. Local token bucket (single process)
client := expox.NewClient(
    expox.WithLimiter(limiter.NewLocal(600, 100)), // 600/s, burst 100
)

// 3. Distributed Redis limiter (multi-pod)
import redislimiter "github.com/Trashwbin/expo-server-sdk-go/limiter/redis"

client := expox.NewClient(
    expox.WithLimiter(redislimiter.New(redisClient, "", 600, 100)),
)

Client Options

expox.NewClient(
    expox.WithAccessToken("token"),      // Expo access token
    expox.WithHost("https://exp.host"),  // API host
    expox.WithBatchSize(100),            // Messages per batch
    expox.WithMaxConcurrent(6),          // Concurrent HTTP requests
    expox.WithMaxRetries(3),             // 429 retry attempts
    expox.WithRetryMinTimeout(time.Second), // Initial retry backoff
    expox.WithGzipThreshold(1024),       // Gzip if payload > N bytes
    expox.WithLimiter(l),                // Rate limiter
    expox.WithHTTPClient(hc),            // Custom http.Client
)

Uber Fx Integration

import "github.com/Trashwbin/expo-server-sdk-go/expofx"

fx.New(
    fx.Provide(func() expofx.Config {
        return expofx.Config{
            AccessToken: os.Getenv("EXPO_ACCESS_TOKEN"),
            RateLimit:   600,
            Burst:       100,
        }
    }),
    expofx.Module,
)

Package Structure

expo-server-sdk-go/
├── expo.go          # Client, options, transport
├── send.go          # Send, SendSingle, SendBroadcast
├── receipt.go       # GetReceipts, ProcessReceipts
├── types/           # Message, Ticket, Receipt, errors
├── limiter/         # Limiter interface, Noop, Local
│   └── redis/       # Distributed Redis limiter
└── expofx/          # Uber Fx integration

License

MIT

Documentation

Overview

Package expox is a Go SDK for the Expo Push Notification Service.

It provides a production-ready client with batching, gzip compression, rate limiting, 429 retry with exponential backoff, and receipt polling.

Types live in expox/types, limiters in expox/limiter. For convenience, commonly used types are aliased here so callers can use expox.Message instead of types.Message.

Index

Constants

View Source
const (
	Version = "0.1.0"

	DefaultHost     = "https://exp.host"
	DefaultBasePath = "/--/api/v2"

	DefaultBatchSize        = 100
	DefaultReceiptBatchSize = 300
	DefaultMaxConcurrent    = 6
	DefaultHTTPTimeout      = 30 * time.Second
	DefaultRetryMinTimeout  = 1 * time.Second
	DefaultMaxRetries       = 3
	DefaultGzipThreshold    = 1024

	DefaultRateLimit = 600
	DefaultBurst     = 100

	// MaxPayloadSize is the maximum total notification payload size (bytes).
	MaxPayloadSize = 4096
)

Variables

View Source
var (
	DefaultSound = types.DefaultSound

	ErrDeviceNotRegistered  = types.ErrDeviceNotRegistered
	ErrInvalidCredentials   = types.ErrInvalidCredentials
	ErrMessageTooBig        = types.ErrMessageTooBig
	ErrMessageRateExceeded  = types.ErrMessageRateExceeded
	ErrMismatchSenderId     = types.ErrMismatchSenderId
	ErrInvalidProviderToken = types.ErrInvalidProviderToken
	ErrProviderError        = types.ErrProviderError
)

Re-export constants and constructors.

Functions

func ChunkMessages

func ChunkMessages(msgs []Message, n int) [][]Message

ChunkMessages splits messages into chunks of at most size n.

func ChunkReceiptIDs

func ChunkReceiptIDs(ids []string, n int) [][]string

ChunkReceiptIDs splits receipt IDs into chunks of at most size n.

func IsValidPushToken

func IsValidPushToken(token string) bool

IsValidPushToken checks whether the token is a valid Expo push token.

func ValidatePayload

func ValidatePayload(msg Message) error

ValidatePayload checks that a message's JSON payload does not exceed MaxPayloadSize.

Types

type APIError

type APIError = types.APIError

type APIErrorEntry

type APIErrorEntry = types.APIErrorEntry

type BatchResult

type BatchResult = types.BatchResult

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is an Expo push notification client.

func NewClient

func NewClient(opts ...Option) *Client

NewClient creates a new Expo push notification client.

func (*Client) GetReceipts

func (c *Client) GetReceipts(ctx context.Context, ticketIDs []string) (map[string]Receipt, error)

GetReceipts fetches push notification delivery receipts by ticket IDs. IDs are automatically chunked into batches.

func (*Client) ProcessReceipts

func (c *Client) ProcessReceipts(ctx context.Context, ticketIDs []string) ([]ReceiptResult, error)

ProcessReceipts fetches receipts and returns structured results. Useful for identifying tokens to remove (DeviceNotRegistered) or credentials to refresh (InvalidCredentials).

func (*Client) Send

func (c *Client) Send(ctx context.Context, messages []Message) (*BatchResult, error)

Send sends push notifications in batches concurrently. Concurrency is bounded by the Client's connection semaphore (MaxConcurrent). Partial batch failures are collected into the result rather than aborting.

func (*Client) SendBroadcast

func (c *Client) SendBroadcast(ctx context.Context, tokens []string, msg Message) (*BatchResult, error)

SendBroadcast sends the same notification content to many recipients concurrently. Tokens are batched and sent using Expo's array-to field for efficiency.

func (*Client) SendSingle

func (c *Client) SendSingle(ctx context.Context, msg Message) (*SendResult, error)

SendSingle sends a push notification to a single recipient.

type ErrorCode

type ErrorCode = types.ErrorCode

type InterruptionLevel

type InterruptionLevel = types.InterruptionLevel

type Limiter

type Limiter = limiter.Limiter

type Message

type Message = types.Message

type Option

type Option func(*Client)

Option configures the Client.

func WithAccessToken

func WithAccessToken(token string) Option

func WithBatchSize

func WithBatchSize(size int) Option

func WithGzipThreshold

func WithGzipThreshold(n int) Option

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

func WithHost

func WithHost(host string) Option

func WithLimiter

func WithLimiter(l limiter.Limiter) Option

func WithMaxConcurrent

func WithMaxConcurrent(n int) Option

func WithMaxRetries

func WithMaxRetries(n int) Option

func WithReceiptBatchSize

func WithReceiptBatchSize(size int) Option

func WithRetryMinTimeout

func WithRetryMinTimeout(d time.Duration) Option

type Priority

type Priority = types.Priority

type Receipt

type Receipt = types.Receipt

type ReceiptResult

type ReceiptResult = types.ReceiptResult

type RichContent

type RichContent = types.RichContent

type SendResult

type SendResult = types.SendResult

type Sound

type Sound = types.Sound

type Ticket

type Ticket = types.Ticket

Directories

Path Synopsis
Package expofx provides Uber Fx integration for expox.
Package expofx provides Uber Fx integration for expox.
Package limiter provides rate limiting for the Expo Push API.
Package limiter provides rate limiting for the Expo Push API.
redis
Package redis provides a distributed rate limiter for expox backed by Redis.
Package redis provides a distributed rate limiter for expox backed by Redis.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL