Expand description
Provider-neutral push delivery with an Expo adapter.
The crate separates a small PushSender port from its ExpoPushSender
adapter. Domain code builds PushMessage values and reads PushOutcome
values; nothing above the port names a provider. Scheduling, quiet hours,
and deciding who gets notified stay in the product.
§Two-phase delivery
Expo answers a send with a ticket per notification, then confirms delivery
later through a receipt. ExpoPushSender runs both phases per batch, so
one call returns PushDeliveryStatus::Delivered or
PushDeliveryStatus::Rejected wherever Expo has settled, and
PushDeliveryStatus::Accepted where it has not. Never resend on
Accepted; the notification is in flight.
§Deferred receipts
An Accepted outcome carries the provider’s PushTicketId. Record those
with PendingReceiptStore::record_accepted and let the product’s job
runner call poll_pending_receipts, which reads the receipts through the
PushReceiptSource port and invalidates a DeviceNotRegistered token
that Expo only reported after the send. The crate ships no scheduler.
§Device registry
A device token stops working once the app is uninstalled. Expo reports that
as DeviceNotRegistered, which becomes
PushRejection::DeviceNotRegistered. The DeviceRegistry port stores
each owner’s tokens and removes the dead ones in one call after a send:
use baukit_push::{DeviceRegistry, PushMessage, PushSender};
use chrono::Utc;
async fn deliver(
sender: &impl PushSender,
registry: &impl DeviceRegistry,
messages: Vec<PushMessage>,
) -> Result<u64, Box<dyn std::error::Error>> {
let sent_at = Utc::now();
let outcomes = sender.send(messages).await?;
Ok(registry.invalidate_dead_tokens(&outcomes, sent_at).await?)
}The registry also registers, rotates, unregisters, lists, and erases
tokens, and caps devices per owner by evicting the least recently
registered. The optional sqlx-postgres feature adds
PostgresDeviceRegistry over the schema in
POSTGRES_PUSH_DEVICES_MIGRATION_SQL. DeviceToken redacts itself in
Debug, so PushMessage and PushOutcome are safe to log, and no
error carries a token.
§Daily delivery claims
A scheduled sender claims a DeliveryClaim keyed by owner, local date,
and kind before it sends, and releases it if the send fails, so two workers
never deliver the same daily notification twice. The DeliveryClaimStore
port is separate from the registry; event-driven senders skip it.
Eligibility, copy, quiet hours, and channel choice stay in the product.
§Retries
Whole-request failures arrive as PushError::Transport carrying a
baukit_http::RetryClass, so an Expo rate limit with a Retry-After
header reaches the caller as a concrete delay through
PushError::retry_after. Per-notification refusals are not errors:
inspect PushRejection::is_retryable on each outcome instead.
Structs§
- Delivery
Claim - The right to send one kind of notification to one owner on one local date.
- Delivery
Kind - A product-defined name for one kind of scheduled notification.
- Device
Registration - One device’s request to receive pushes for an owner.
- Device
Time Zone - The time zone a device reported when it registered, as an IANA name.
- Device
Token - A provider-issued device token.
- Expo
Push Sender - Expo push adapter.
- Pending
Receipt - An accepted notification whose receipt has not been read yet.
- Push
Config - Deserializable push configuration for a product’s config section.
- Push
Message - One notification addressed to a single device token.
- Push
Options - Validated options an
ExpoPushSenderis built from. - Push
Outcome - What happened to one notification, keyed by the token it was addressed to.
- Push
Ticket Id - The ID a provider issued for one accepted notification.
- Receipt
Poll - What one
poll_pending_receiptsrun did. - Registered
Device - A device as the registry stores it.
- Registration
Outcome - What a registration or rotation changed besides the registered device.
Enums§
- Device
Platform - The mobile platform a device token belongs to.
- Push
Delivery Status - The delivery state a provider reported for one notification.
- Push
Error - A failure that prevented the whole batch from being processed.
- Push
Options Error - Invalid push adapter configuration.
- Push
Receipt - The settled receipt for one accepted notification.
- Push
Rejection - Why a provider rejected one notification.
- Push
Store Error - A store failure.
- Push
Validation Error - A value rejected before it reached a store.
- Receipt
Poll Error - A failure that stopped a receipt poll.
Constants§
- DEFAULT_
DEVICES_ PER_ OWNER - Devices an owner keeps when a store is built without an explicit cap.
- DEFAULT_
EXPO_ ENDPOINT - Expo’s public push send endpoint.
- MAX_
BATCH_ SIZE - Largest batch Expo accepts in one
/push/sendrequest. - MAX_
DELIVERY_ KIND_ LENGTH - Longest delivery kind accepted, in bytes.
- MAX_
DEVICE_ TIME_ ZONE_ LENGTH - Longest device time zone name the registry accepts, in bytes.
- MAX_
DEVICE_ TOKEN_ LENGTH - Longest device token the registry accepts, in bytes.
- MAX_
PUSH_ TICKET_ ID_ LENGTH - Longest provider ticket ID accepted, in bytes.
- MAX_
RECEIPT_ BATCH_ SIZE - Most ticket IDs Expo accepts in one
/push/getReceiptsrequest. - POSTGRES_
PUSH_ DELIVERY_ CLAIMS_ MIGRATION_ SQL - Reference PostgreSQL schema for the
sqlx-postgresfeature’sPostgresDeliveryClaimStore. - POSTGRES_
PUSH_ DEVICES_ MIGRATION_ SQL - Reference PostgreSQL schema for the
sqlx-postgresfeature’sPostgresDeviceRegistry. - POSTGRES_
PUSH_ PENDING_ RECEIPTS_ MIGRATION_ SQL - Reference PostgreSQL schema for the
sqlx-postgresfeature’sPostgresPendingReceiptStore. - POSTGRES_
PUSH_ PENDING_ RECEIPT_ OWNERS_ MIGRATION_ SQL - Reference PostgreSQL migration that adds the owner to each pending ticket.
- RECEIPT_
POLL_ DELAY - How long a recorded ticket waits before its first poll, and between polls.
- RECEIPT_
RETENTION - How long the provider keeps a receipt after the send.
Traits§
- Delivery
Claim Store - Storage port for daily delivery claims.
- Device
Registry - Storage port for the push tokens each owner’s devices registered.
- Pending
Receipt Store - Storage port for tickets whose receipts are still outstanding.
- Push
Receipt Source - Outbound port for reading the receipts of accepted notifications.
- Push
Sender - Outbound port for delivering notifications to devices.
Functions§
- accepted_
receipts - Returns a pending receipt for each accepted outcome of one send to
owner_id. - dead_
tokens - Returns the distinct tokens a send reported as dead.
- poll_
pending_ receipts - Polls one batch of due receipts and applies what settled.
Type Aliases§
- Push
Future - The future returned by
PushSender::send. - Push
Receipt Future - The future returned by
PushReceiptSource::receipts. - Push
Store Future - The future returned by
DeviceRegistryandDeliveryClaimStoremethods.