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.

SettingNotes
enabledTurns the reply on. An enabled auto-reply needs a message; saving it with a blank one is refused.
messageThe reply text, at most 320 characters. Anything longer is cut at 320.
officeHours.tzAn IANA time zone such as America/New_York. The console fills in your browser's zone.
officeHours.daysThe days you are open, as numbers: 0 is Sunday, 6 is Saturday. The console starts with Monday to Friday.
officeHours.start, officeHours.endOpening and closing time as 24-hour HH:MM. The console starts with 09:00 and 17:00.
officeHours.modealways 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 price is 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:

json
{
  "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.

bash
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.