Skip to main content
SendService has two methods. Send takes one message and gives one answer, and mirrors POST /secure/send field for field. SendStream carries many messages on one bidirectional stream, for sustained volume. Both need the session token from Login in the authorization metadata.

Send: one message

Accepted means queued for delivery, not delivered — exactly as a 200 does on REST. The outcome reaches you only as a delivery receipt.

Fields

Every field except to is optional: a field you do not set behaves exactly as a JSON key you leave out of a REST request, and takes the same default. The same approvals, products, TLV grammar and limits apply, because both reach the same checks. REST’s dlr_method and tags are not carried; their field numbers are reserved so they can never be reused for something else.

custom_tlvs values

Each value is a TlvValue holding exactly one of: The key decides the wire encoding, exactly as on REST. "0x1401:Int8" with int_value: 42 is the 8-byte integer 42, as "0x1401:Int8": 42 is on REST.
In proto3 JSON — what grpcurl reads — a 64-bit integer is written as a string. Generated clients take a native integer.

Receipts: dlr and dlr_url

The webhook always wins: a message with a dlr_url never appears on the feed.
If your provider has switched the receipt feed off, dlr: true without a dlr_url is refused INVALID_ARGUMENT naming dlr_url — the same refusal REST gives — because the receipts would have nowhere to go.

The answer

price is a string on purpose: a decimal amount survives every language unchanged that way, where a floating-point number would not.

Retrying safely with client_ref

Set client_ref on every send you might retry. A repeat inside 24 hours from the same account — over gRPC or REST, whichever login sent it — is answered with the first send’s message_id and duplicate: true, and nothing is sent or charged again. A send that was refused frees its reference, so the corrected request may reuse it. The rules are REST’s, described in full under Retrying safely with client_ref — including the one narrow gap on a gateway cluster. This matters most for one answer in particular: UNKNOWN with OUTCOME_UNKNOWN, where the message may be on its way. Retry that only with the same client_ref. See Errors and status codes.

SendStream: many messages on one stream

For sustained high volume. You write SendStreamRequests and read SendStreamResults on one bidirectional stream. Each request is a correlation_id of your choosing and a SendRequest — exactly the message you would pass to Send. Each result is: correlation_id must be 1–64 characters. An empty or longer one is answered — for that request only — INVALID_ARGUMENT with field: correlation_id. Use something unique per request on the stream; your client_ref is often the natural choice.

One result per request, in any order

Requests are submitted concurrently, so a quick one overtakes a slow one. Match on correlation_id, never on position.

A refusal is a result, not a stream error

An empty balance, the rate limit or a bad field answers that one request, and the stream carries on. The stream itself ends only when you close your side — then with OK, after the last result — or when the gateway stops.

Read continuously, or the stream stalls

The gateway holds at most 256 of your requests at once on one stream — being submitted, or answered and waiting for you to read the result. Past that it stops reading from the stream until a result has been read, and ordinary HTTP/2 flow control holds your writes back. So: write as fast as your library lets you, and read results continuously on another thread or task. A client that writes everything first and reads afterwards stops after 256. For more concurrency, open more streams — each has its own 256. They all share your account’s messages-per-second limit, so more streams buy concurrency, not rate.

When the gateway stops

A gateway being restarted ends its streams cleanly:
  1. Requests already being submitted finish, and their results are sent.
  2. Requests received but not yet started are answered UNAVAILABLE / SHUTTING_DOWN without being submitted.
  3. The stream ends with UNAVAILABLE (SHUTTING_DOWN).
Reconnect — to another node, or to this one when it is back — log in again, and resend only what has no result, with the same client_ref, so nothing is sent twice. A connection that drops without that orderly end leaves you with requests that have no result. Resend those too, with the same client_ref: whichever were accepted come back duplicate: true.

Examples

Send and SendStream in Python, Java and Go.

POST /secure/send

The REST twin of every field above.