Smart Error Handling
Admin → Automations → Create New Automation → Message Errored
Our Message Errored Automation lets you define what happens each time an error occurs.

You can paste or code snippets into the JavaScript snippet box.

And/or you can add no-code actions!

This automation enabled fine-grained control of what happens when an error code is received on an outbound message. Some example uses of this feature include:
Counting the number of errors a contact has received and if exceeds a given threshold, automatically opt the contact out or add a tag.
Remove contacts from certain lists if a given error code is received
Count "spam" reports for an outgoing number and remove the number from lists if a count is exceeded
Here are FIVE snippet examples that you can copy/paste into the "Message Errored" Automation:
Snippet 1: Tag obvious landlines / bad numbers as likelyNonTextable
If the error code strongly suggests that the number is not a cell, then it's probably not worth including in future broadcasts.
This snippet will automatically add a tag, likelyNonTextable
Optionally, the last line can be un-commented (remove the // to activate it). Doing so, will mean that the snippet automatically removes the contact from all lists.
// Array of low-confidence textability error codes (indicating likely bad/landline/unsubscribed numbers)
var lowCodes = [4303, 4350, 4406, 4420, 4421, 4433, 4434,
4492, 4700, 4701, 4702, 4720, 4721, 4753,
4773, 4774, 4775, 21212, 21217, 21610, 30003,
30006, 21211, 21614];
// If the error code isn't low-confidence, do nothing.
if (!error.code || lowCodes.indexOf(Number(error.code)) == -1) return;
// Tag the customer as likely non-textable.
customer.addTag("likelyNonTextable", "#f05a28");
// platform.removeFromAllContactLists();
Snippet 2: Tally ambiguous failures & mark Error-prone contacts *RECOMMENDED
Same as snippet 1, but for each contact, a data-field called error_tally is incremented.
When error_tally becomes 2 or more, then the contact gets tagged with Error-prone
// Combined list of low- and medium-confidence error codes
var tallyCodes = [
// Low-confidence: clearly landline / bad / unsubscribed
4303, 4350, 4406, 4420, 4421, 4433, 4434,
4492, 4700, 4701, 4702, 4720, 4721, 4753,
4773, 4774, 4775, 21212, 21217, 21610, 30003,
30006, 21211, 21614,
// Medium-confidence: ambiguous delivery errors
4401, 4409, 4482, 4730, 4750, 5106, 5500,
5501, 5999, 9999, 30005, 30004, 30008,
63011, 30011, 21219
];
// Make sure we have a code and that it’s in the list
if (!error.code || tallyCodes.indexOf(Number(error.code)) == -1) {
return; // => non-qualifying error; do nothing
}
// ----- Increment the customer’s running error tally -----
var tally = Number(store.customer.error_tally || 0) + 1;
store.save('customer', 'error_tally', tally);
// Tag after ≥2 strikes
if (tally >= 2) {
customer.addTag("Error-prone", "#f05a28");
}
Snippet 3: Smart Error Handler; auto‑clean lists (remove / abolish)
Gradually cleans your lists over time. Your texting campaigns will have very low error rates as you keep targeting the same contacts, and our system learns about their textability!
Does everything that the above two snippets offer (i.e., Error-prone tagging), but also including...
Per-error-code tracking on each contact (i.e., Jane Doe received one 30005 error and two 30008 errors.
Weighted thresholding (i.e., if Jane Doe gets two 30003, we'll completely abolish her, but she needs three 30008 errors in order to be abolished)
More aggressive "abolishing" when high threshold is met (i.e., remove from all lists, add "Abolished" tag to always suppress).
// Mapping: error.code -> removal threshold.
// Low-confidence errors (likely bad number/unsubscribed): threshold 2
// Medium-confidence errors (ambiguous issues): threshold 3
var thresholds = {
// Low confidence
4303:2, 4350:2, 4406:2, 4420:2, 4421:2, 4433:2, 4434:2,
4492:2, 4700:2, 4701:2, 4702:2, 4720:2, 4721:2, 4753:2,
4773:2, 4774:2, 4775:2, 21212:2, 21217:2, 21610:2, 30003:2,
30006:2, 21211:2, 21614:2,
// Medium confidence
4401:3, 4409:3, 4482:3, 4730:3, 4750:3, 5106:3, 5500:3,
5501:3, 5999:3, 9999:3, 30005:3, 30004:3, 30008:3,
63011:3, 30011:3, 21219:3
};
// If the error code isn't mapped, assume it's high-confidence.
// Whisper a message including the error.code and error.description, then return.
var th = thresholds[error.code];
if (!th) {
platform.whisper("An error occurred while processing this message " + error.code + ": " + error.description);
return;
}
// Function to abolish the customer.
function abolishCustomer() {
customer.addTag("Abolished", "#ff0000");
platform.removeFromAllContactLists();
}
// Increment a general tally for low/medium errors.
var tally = Number(store.customer.error_tally || 0);
tally++;
store.save('customer', 'error_tally', tally);
if (tally >= 2) {
customer.addTag("Error-prone", "#f05a28");
}
// Increment the counter for this specific error code.
var key = 'errored' + error.code;
var count = Number(store.customer[key] || 0);
count++;
store.save('customer', key, count);
if (count >= th) {
abolishCustomer();
}
Snippet 4: Inbound Message Detection
One of the best indicators of whether a phone is textable is whether or not they've sent an inbound message
If they've ever messaged you, but they're erroring, that means you probably don't want to remove them from lists, so this will simply tag them with hasSentInboundMessage
If they've messaged you recently, not only is it a stronger indicator that they are a textable cell, but also that they are probably willing to engage. This snippet tags (or untags) activeRecent if they've sent an inbound message within the last 7 days.
// Get the last inbound message timestamp (milliseconds since epoch)
var lastMsg = Number(store.conversation.lastCustomerMessage || 0);
// If there's a valid timestamp, tag the customer accordingly.
if (lastMsg) {
// Always tag customers who've ever sent an inbound message.
customer.addTag("hasSentInboundMessage", "#f05a28");
// Calculate how long ago the last inbound message was.
var now = new Date().getTime();
var diffMs = now - lastMsg;
// For example, consider 7 days (in ms) as "recent".
var sevenDays = 7 * 24 * 60 * 60 * 1000;
if (diffMs < sevenDays) {
customer.addTag("activeRecent", "#f05a28");
} else {
// Optionally remove the "activeRecent" tag if it's stale.
customer.removeTag("activeRecent");
}
// Optionally log the time difference for debugging.
var diffDays = Math.floor(diffMs / (24 * 60 * 60 * 1000));
var diffHours = Math.floor((diffMs % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
platform.whisper("Last inbound message was " + diffDays + " days and " + diffHours + " hours ago.");
} else {
// If no inbound message exists, ensure the tags are removed.
customer.removeTag("hasSentInboundMessage");
customer.removeTag("activeRecent");
}
Snippet 5: Detect spam‑filter strikes per channel
This snippet tracks how many times a spam-based error has occurred on the channel.
When a threshold is met, it is possible to have it pause the broadcast so that you can catch spam issues early on. Just un-comment the line // platform.pauseBroadcast() by removing the // characters so that it activates platform.pauseBroadcast()
The spam tally for the channel resets when the threshold is met.
// Define spam filter error codes as an array.
var spamCodes = [30007, 4470, 4770, 4771, 4772];
// If the error code is not in our spam filter list, exit.
if (!error.code || spamCodes.indexOf(Number(error.code)) == -1) return;
// Increment the spam tally for the channel.
var spamTally = Number(store.channel.spam_tally || 0);
spamTally++;
store.save("channel", "spam_tally", spamTally);
// Optionally log the spam tally for debugging.
platform.whisper("Channel spam tally: " + spamTally + " for error " + error.code);
// Define a threshold for spam filtering.
var threshold = 3;
if (spamTally >= threshold) {
// Take action on the channel if spam errors exceed the threshold.
// platform.pauseBroadcast()
// make an external API request
// platform.buildCallExternalWebAPI(...)
// restore channel tally
store.save("channel", "spam_tally", 0)
platform.whisper("Channel " + channel.id + " exceeded spam threshold. Please review message content.");
}
Additional Information
Carrier‑Level vs. Message‑Level Failures
Carrier rejection notifications already protect entire broadcasts: you set an error‑rate threshold, we pause or cancel when it’s breached. But a smart campaign should consider handling errors at the message level as well.
The Core Question
"What should happen when an individual message fails to deliver?"
Why the Answer Is “It Depends”
Error codes fall into broad buckets, and each bucket deserves a different reaction:
Bucket | Example codes | Typical action |
landline / unsub / permanently bad | 4303, 4700, 30003 | Tag likelyNonTextable, remove from all lists |
ambiguous or transient | 30005, 4401 | Increment tally; when tallies ≥ 2, tag Error-prone |
Spam filter | 30007, 4470 | Increment channel tally; pause broadcast at 3 |
Other / unknown | anything else | Whisper alert so you can inspect |
Using the Tags & Tallies
Once tags (likelyNonTextable, Error-prone, hasSentInboundMessage, etc.) or data fields (error_tally, spam_tally) are in place, you decide what happens next:
Suppress vs. include: In Broadcast Settings you can exclude Error-prone contacts when deliverability is vital, or choose not to suppress them when maximum outreach is needed.
Segment & retarget: Build Contact Queries such as error_tally ≥ 3 or NOT hasSentInboundMessage to send tailored follow‑ups.
A/B test policies: Send two identical broadcasts—one that suppresses likelyNonTextable, one that doesn’t—to measure the impact of strict list hygiene.
Trigger more automations: Fire additional automations whenever a tag is added/removed (e.g., pause a broadcast platform.pauseBroadcast(), or make an external API request to another application platform.buildCallExternalWebAPI(...)
These snippets purposely label contacts; the strategy for using those labels stays flexible and 100% under your control.