202 comes back with status: scheduled.
Every POST /v1/messages also counts against the message-operations limit of your network address: 60 requests per rolling 60 seconds for POST /v1/messages and GET /v1/messages together, whatever the line and the account. Above it, the API returns 429 rate_limit_exceeded: see Rate limits. This limit, not the sending limits of a line, bounds how many messages one server sends per minute through POST /v1/messages, across all your lines.
The limits and holds of this page apply to sends with an account API key, on every kind of line. A send with a trial key is outside them: the trial has its own unanswered-send limit.
The five limits
Each line has five sending limits. All five count over a rolling window: no counter resets at midnight.
The defaults are settings of iSnap. The limits in force on a line are the defaults of its kind of device, replaced by the line’s own settings where it has any. Read the values in force with
GET /v1/lines/{id}/quota, and read or change the settings with GET /v1/lines/{id}/config and PATCH /v1/lines/{id}/config: see Configuration. A limit can be lowered below its default, down to its minimum, and raised above it only on a BYOD line, up to its maximum.
iSnap checks the per-minute limit first, then the per-hour limit, then the 24-hour message limit, then the contact limit that applies to the recipient. The first limit reached is the one reported.
New and known contacts
A recipient is known when that number sent at least one message to the line in the last 30 days, by default. Every other recipient is new. Sending to someone does not make them known: only a message from them does. iSnap decides the class again at each send. The rules that depend on a contact having written to the line (known contacts, inbound-only lines and the reply lane) compare theto of your send with the sender handle as the line’s device stored it: see Inbound messages.
What counts
- Each message accepted by
POST /v1/messagesor byPOST /v1/chats/{chat_id}/share-contact-cardcounts once in the three message counters. - The two contact counters count each distinct recipient once per 24 hours, however many messages the recipient gets.
- A message counts when it leaves, not when iSnap accepts it: a held message counts only once released.
- Reactions, typing indicators, read receipts, lookups, inbound messages and sends with a trial key count against no limit.
Held messages
A held message is a message withstatus: scheduled. iSnap holds it until scheduled_for, and schedule_reason says why. When a send would go over one of the five limits, the line’s quota_overflow_behavior decides:
Spacing, quiet hours, the order of messages to one recipient and, on a shared line, a number that cannot take the message, hold a message in every mode. They return
429 queue_full only when the line already holds its maximum: see Maximum number of held messages.
A message held behind an earlier message to the same recipient takes the
schedule_reason of that earlier message while the earlier one is held, and inter_message_delay (carrier_retry on a shared line) while the earlier one waits for the line’s device. schedule_reason alone does not tell you that a message waits for an earlier one: look for an earlier message to the same to in GET /v1/lines/{id}/queue. See Order.
Ignore response fields, enum values and event types you do not know.
How long a hold lasts
When the end of a per-minute or per-hour hold falls in quiet hours, the message is held until the next hour in which the line sends. A message in the reply lane, and a message sent with
ignore_quiet_hours: true, are held until the end of the window instead.
A held message that has not left 48 hours after iSnap accepted it fails with error_code: carrier_unavailable, even when its scheduled_for is later. It fails at least 48 hours after its acceptance, and the API does not bound the delay beyond that. A 202 whose scheduled_for is more than 48 hours after created_at describes a message that can fail before it leaves: cancel it, and resend it when the limit frees room. See Failures.
Release
iSnap looks at the held messages every 5 seconds. When thescheduled_for of a message arrives, iSnap checks it again against the limits, the spacing, quiet hours and the order of messages to the recipient. Then it either releases the message, which becomes queued and sends message.queued, or holds it again with a new scheduled_for and schedule_reason.
- A held send produces
message.scheduled, withschedule_reasonandscheduled_for, instead ofmessage.queued. - When iSnap moves the departure of a held message by more than 15 minutes, it sends another
message.scheduled. A smaller move sends nothing: read the message for the currentscheduled_for. A held message that enters the reply lane because its contact wrote sends nomessage.scheduledeither:message.queuedfollows when iSnap releases it. - Once released, the message has
schedule_reason: null, andscheduled_forequalsqueued_at: it no longer shows when the message left.sent_atgives the time the line’s device sent it.
DELETE /v1/messages/{id}: see Canceling a message.
Maximum number of held messages
A line holds at mostmax_queue_depth messages: 500 on an iMessage line and 1,000 on an Android line, by default. A send that iSnap would hold returns 429 queue_full when the line already holds that many, or when iSnap finds no time to send it in the next 8 days. Nothing is stored. Retry-After is the number of seconds until the next midnight UTC. A hold planned more than 48 hours ahead can fail before it leaves: see How long a hold lasts.
Quiet hours
By default, a line sends only between 08:00 and 20:59 in the recipient’s local time. A message accepted outside those hours is held withschedule_reason: quiet_hours until the next allowed time, in every overflow mode. Two kinds of message are not held by quiet hours: a message in the reply lane, and a message sent with ignore_quiet_hours: true.
The hours come from the line’s activity_curve: an hour to which the curve gives a weight of 0, or that the curve does not list, is a quiet hour. GET /v1/lines/{id}/config returns the curve in force on the line in config.activity_curve, and the curve the line has without an override of its own in default_activity_curve. The default curve gives a weight of 0 to every hour from 21:00 to 07:59. It is a setting of iSnap, which can change it: read it instead of storing it. Change the curve of a line with PATCH /v1/lines/{id}/config: see Configuration.
Which clock iSnap reads:
On a shared line, the time zone of the line, the
activity_curve and the spacing are those of the number that serves the contact: the timezone, activity_curve and spacing settings of the shared line do not apply.
Sending during quiet hours
ignore_quiet_hours: true on POST /v1/messages or on POST /v1/chats/{chat_id}/share-contact-card lets a message leave during the quiet hours of the line. Use it for a recipient of your own, such as an alert to your own phone number. The default is false.
- Quiet hours never hold such a message, neither when iSnap accepts it nor when iSnap releases it.
- The message still waits for the sending limits of the line, for the spacing, and behind an earlier message to the same recipient that iSnap holds, whatever holds that earlier message. It then takes the
schedule_reasonof that earlier message,quiet_hoursincluded. It counts againstmax_queue_depthlike any held message. - A hold for the per-minute or per-hour limit ends at the end of the window, even in quiet hours. A hold for a 24-hour limit still ends in an hour in which the line sends.
- On a shared line, the option has no effect.
- The message returns the value in force in
ignore_quiet_hours, set when iSnap accepts it and never changed:falseon a shared line, on an inbound message and on a message typed on the line’s device.
Spacing
Below every limit, iSnap still spaces the messages of a line. Spacing holds a message withschedule_reason inter_message_delay or inter_new_contact_delay, in every overflow mode.
With the defaults, an iMessage line sends first messages to at most 12 new contacts per hour. The two settings can only be raised above their defaults: see Configuration.
Reply lane
An answer to a contact who wrote to the line in the last 24 hours is not stopped by the per-hour limit, the three 24-hour limits or quiet hours. The per-minute limit, the spacing and the order of messages to the recipient still apply.reply_lane on the message is true when the message is in the reply lane. A message uses the daily_reply_limit budget only when the reply lane let it pass the per-hour limit or a 24-hour limit. Passing quiet hours uses no budget. Reactions never use the reply lane. The three settings can only be lowered below their defaults.
Held messages that enter the reply lane
When a contact writes to a line, the messages that iSnap holds for that contact on that line enter the reply lane, whatever held them:- They enter oldest first. As many enter as
reply_max_per_inboundallows for that message of the contact, counting the answers that follow it, and no more than the room left indaily_reply_limit. The first held message that does not fit keeps itsscheduled_for, and so do the later ones. - A message that enters the lane changes
reply_lanefromfalsetotrue, once and never back. - It leaves without waiting for its
scheduled_for, still after the earlier messages to the same recipient, and within the per-minute limit and the spacing of the line. On a shared line, it also waits until the number that serves the contact can take it. - iSnap sends no
message.scheduledfor this change:message.queuedfollows when iSnap releases the message. - When iSnap cannot move them, the held messages wait for their
scheduled_for.
Order
Messages to the same recipient on the same line leave iSnap in the order iSnap accepted them. While an earlier message to that recipient is held or waiting for the line’s device, a later message is held behind it, at least the line’s message spacing later. It takes theschedule_reason of the earlier message while the earlier one is held, and inter_message_delay (carrier_retry on a shared line) while the earlier one waits for the line’s device. Reactions are outside this rule.
The line’s device keeps that order: it sends one item at a time, 0.5 second apart give or take 15%, and sends the messages to one recipient in the order it received them. A message that the device retries holds back the later messages to that recipient only. Reactions, typing indicators and read receipts take their turn in the same sequence on the device.
The order is not guaranteed beyond that:
- iSnap does not guarantee the order in which the services deliver the messages.
- There is no order between messages to different recipients.
- When an earlier message fails or is canceled, the later messages to that recipient still leave.
- Two requests for the same recipient that arrive at the same time can be accepted in either order. To fix the order, send the second request after the response to the first.
Headers on a send
A202 from POST /v1/messages sent with an account API key carries two headers:
The counters are rolling, so
X-iSnap-Quota-Resets-At is not the time at which the limit frees room: read daily_new_frees_at on GET /v1/lines/{id}/quota for that. The two headers are also on the five 429 responses of the limits, and not on 429 queue_full. A send with a trial key carries neither, and neither does POST /v1/chats/{chat_id}/share-contact-card.
The SDK does not return response headers: see Read response headers with fetch.
Refusals
On a line whosequota_overflow_behavior is drop or fail, a send over a limit returns 429 with the code of that limit, Retry-After and error.retry_after:
For the three 24-hour limits,
Retry-After is a hint: the rolling window can free room earlier or later. The *_frees_at fields of GET /v1/lines/{id}/quota give the time each limit frees room. 429 queue_full is described in Maximum number of held messages.
These codes are not the request-rate limit, which counts requests per network address: see Rate limits. In the SDK, minute_limit_exceeded and hourly_limit_exceeded are a RateLimitError, and the three other codes and queue_full are a QuotaExceededError: see The two error classes of a 429.
The SDK retries a RateLimitError by itself when Retry-After is 60 seconds or less: on a line set to refuse, it retries a minute_limit_exceeded request 60 seconds later. Pass maxRetries: 0 in the options of the call to receive the refusal immediately.
Messages that stop counting
A message stops counting against the sending limits in two cases:- A message you cancel, when it had counted. A message canceled while held had not counted.
- A message that iSnap fails itself:
error_codecarrier_unavailable,no_terminal_ackorcarrier_terminated.
Events of the sending limits
- Each event is sent at most once per line, per counter, per 24 hours.
- iSnap evaluates them when a message leaves. A send refused with
429, and a send that iSnap holds, sends neither. datacarriesline_id,bucket(daily_new,daily_known,daily_total,hourlyorminute),used,limitandresets_at.resets_atis the time of the event plus the length of the window: it is not the time the limit frees room.- No event announces that a counter falls back below a threshold. Read
GET /v1/lines/{id}/quota: see What events do not announce.
Reading the limits and the queue
GET /v1/lines/{id}/quota returns the state of the five limits of a line and of its reply lane:
GET /v1/lines/{id}/queue lists the outbound messages of the line that are held, waiting for the line’s device, or sent without a delivery receipt, in the order of scheduled_for, with a summary of the counters. Each item has a status of its own:
A
delivered_to_device item stays listed until a receipt arrives or iSnap deletes the message, at least 72 hours after it recorded it. On a line whose messages get no delivery receipt, such as messages sent as SMS or RCS, these items pile up for at least 72 hours: filter with status=scheduled or status=queued to see what is still to leave.
404 not_found for a line that is not yours. A line-scoped key gets 403 forbidden for a line outside its list, even when the line does not exist.
The items of GET /v1/lines/{id}/queue carry personal data: the recipient’s number in to and the text in body. Request them only when needed. Do not copy them into logs, tickets or other tools. Refer to a contact by an id of your own.