Auto-replies & office hours
Each number can answer inbound texts automatically: an away message, an out-of-office, or a first-touch acknowledgement.
Configuration
Auto-replies are set in the console, not through the API. Open Console → Numbers, expand Auto-reply under a number, and save. Each number has its own settings, and only a number that is currently on your account can have one.
| Setting | Notes |
|---|---|
enabled | Turns the reply on. An enabled auto-reply needs a message; saving it with a blank one is refused. |
message | The reply text, at most 320 characters. Anything longer is cut at 320. |
officeHours.tz | An IANA time zone such as America/New_York. The console fills in your browser's zone. |
officeHours.days | The days you are open, as numbers: 0 is Sunday, 6 is Saturday. The console starts with Monday to Friday. |
officeHours.start, officeHours.end | Opening and closing time as 24-hour HH:MM. The console starts with 09:00 and 17:00. |
officeHours.mode | always or after_hours, described below. |
always: reply to every eligible inbound. The hours are ignored. This is also what you get with no office hours at all.after_hours: the out-of-office pattern: reply only OUTSIDE the hours. Ticking "Only outside office hours" in the console saves this mode.
How office hours are read
You are open when the current day, in your time zone, is one of days and
the time is at or after start and before end. A day that is not listed
is closed all day, so with Monday to Friday hours a Saturday text always gets
the reply. The window cannot cross midnight: if end is earlier than
start you are never open, and after_hours replies around the clock.
Screening and the audit log
The text is checked against the content policy when you save an enabled
auto-reply, and a blocked text is refused with the reason. It is checked once,
at save time, not on every reply. Each save is recorded in the
audit log as settings.updated with target type
auto_reply: whether it is enabled and which hours mode it uses, never the
text.
Guardrails (not configurable)
These are checked in this order on every inbound text:
- STOP / START / HELP keywords always win; the auto-reply never answers them. That covers every keyword and a plain-English request to stop. Those messages get the keyword reply instead, or none.
- Verification codes are never answered. A text counts as one when it is only
a 4 to 8 digit number, when it has a 4 to 8 digit number together with one
of the words code, verification, verify, otp, passcode or pin, or when it
contains a code in the form
G-123456. - Opted-out counterparties are never texted.
- One auto-reply per conversation per 4 hours, claimed atomically; two simultaneous inbound messages cannot double-send.
The 4-hour window belongs to the conversation: one of your numbers and one other party. The same person texting two of your numbers can get a reply from each. The window is claimed before the reply is sent, so a reply that fails at the carrier still uses it.
The reply as a message
An auto-reply is an ordinary outbound message from the number that received
the text. It appears in the inbox thread and in GET /v1/messages.
- It is not billed. Its
priceis 0 with the line "Automatic reply (not billed)". - It does not count against your daily or monthly message quota.
- It has no
sent_by.
Events
The inbound text emits message.received first. The reply then emits
message.sent with two extra fields:
{
"type": "message.sent",
"data": {
"message_id": "msg_a1B2c3D4e5F6g7H8",
"to": "+14155550132",
"from": "+15005550100",
"body": "Thanks for texting! We are closed and will reply in the morning.",
"auto_reply": true,
"kind": "auto_reply"
}
}STOP and HELP confirmations carry auto_reply: true as well, with kind
set to keyword_reply. Check kind to tell the two apart. On a live
number the event also has a carrier_reference.
If a live reply cannot be handed to the carrier, the message is stored as
failed and you get message.failed with code carrier_rejected, a reason, the
failure object, and the same auto_reply and kind fields. Event
delivery is covered in Webhooks.
Testing
Works identically in sandbox: simulate an inbound with
POST /v1/test/inbound and the reply appears in the thread with an
auto_reply: true marker on its event.
curl -X POST https://api.jointextkit.com/v1/test/inbound \
-H "Authorization: Bearer ssms_sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "from": "+14155550132", "body": "Are you open?" }'The call returns 201 with the inbound message, not the reply. Find the
reply in GET /v1/events or in GET /v1/messages?number=%2B14155550132.
A sandbox reply is recorded with status sent and test: true, and is
never transmitted. Send a second text within 4 hours and no reply comes back:
that is the cooldown. The endpoint takes test keys only; a live key gets
403 test_mode_only. Neither SDK has a call for it or for auto-reply
settings.