Ppoppo Docs

Send messages in bulk

This guide sends notifications to Ppoppo users from start to finish. It uses the Messaging External API — see the Messaging reference for the exact services and fields, and the Events reference for delivery events.

You'll need an approved External API client (client_id + client_secret) with the messaging scope. Authentication is a client_credentials JWT, which the pcs-external crate fetches for you.

1. Create a template

Messages are sent from a template. Create one once, with the components you need (Text, Divider, Button, Poll) and {{placeholders}} for per-recipient values. The returned template.id (e.g. tpl_01HQ…) is what you send. See Templates.

2. Send to recipients

For a single pre-formatted string, the curated send_alert path is enough (see Send a message). For per-recipient variables and bulk sends — up to 1,000 recipients per request — use CreateSendRequest:

let resp = client.create_send_request(CreateSendRequestReq {
    template_id: template_id.to_string(),
    recipients: vec![
        RecipientInput { ppnum: "12312345678".into(), vars: Some(build_vars(&[("name", "Alex")])) },
        RecipientInput { ppnum: "12312345679".into(), vars: Some(build_vars(&[("name", "Sam")])) },
    ],
    poll_config: None,
}).await?;

A first-time recipient must consent before they receive anything from your app. A send to a new recipient produces a RECIPIENT_PENDING_CONSENT event, and the message is queued (~24 hours) awaiting their decision. Handle the consent events on the event stream:

match event.event_type() {
    SendRequestEventType::RecipientPendingConsent => mark(&r.ppnum, "pending"),
    SendRequestEventType::ConsentGranted          => mark(&r.ppnum, "granted"),  // delivers automatically
    SendRequestEventType::ConsentDenied           => notify_via_other_channel(&r.ppnum),
    _ => {}
}

4. Track delivery

A send returns an aggregate acceptance receipt, not per-recipient status. For per-recipient outcomes you have two options:

  • Stream StreamSendRequestEvents (preferred) — real-time RECIPIENT_DELIVERED / RECIPIENT_FAILED / REQUEST_COMPLETED events. See Event streaming.
  • Poll GetSendRequestStatus — a snapshot of delivered / pending_consent / failed.

5. Pace your sends and handle errors

The API enforces a per-app rate limit and monthly quota. On RESOURCE_EXHAUSTED, back off using the retry-after-ms hint rather than retrying immediately.

When diagnosing a failing send, remember the recovery ladder: messages like "Invalid token""Template not found" → success are progress, not regression. The full code list is in the Errors reference.

To exercise each delivery and consent outcome before going live, use the sandbox test numbers.