AWRA OpsHub Search

An Error With a Number on It

When a message to five people fails for two of them, the useful information is which two. Most integrations throw that away and report one reason. This one keeps the number on the front of every error, and the difference is a minute of work against an afternoon of it.

Integrations & Data AWRA OpsHub Team 11 min read

There is a moment in every operations job where somebody says the alerts are not working. What follows depends entirely on what the system recorded. If it recorded that a send failed, the next hour is spent establishing whether it failed for everybody or for one person, which is the question that decides whether anything is actually wrong. If it recorded which numbers failed and why, the conversation is over in about ninety seconds.

The number goes on the front of the error

Every failure in this connector is recorded as the recipient, then the reason. Not a count, not the last one — each failure, with the number it belongs to, and all of them returned together.

It costs one string concatenation per failure and it changes the character of the diagnosis entirely.

What is reported What it tells you What you do next
Sending failed Something is wrong Start investigating from nothing
Two of five failed It is not systemic Find out which two
These two numbers failed, with reasons Exactly what is wrong and where Fix the two numbers

Every row down that table is one more piece of context the system already had at the moment of failure and chose whether to keep.

That is the thing worth internalising. None of this is extra work at diagnosis time — it is a decision made at the point of failure, when the information is free, about whether to carry it or discard it. Discarding is the default because it requires writing nothing.

Each recipient has its own request, its own response and its own reason.
Each recipient has its own request, its own response and its own reason.

Why this connector can do it and the others do not

An honest comparison, since two other connectors in this series also send to a list and both keep only the last reason.

The difference is what the provider offers. Twilio takes one request per recipient, so each has its own response and its own error, and keeping them separate is natural. The other two either take the whole list in a single call or return a shape where the reasons are not individually attributable in the same way.

So this is not a case of one connector being written more carefully than another. It is a case of the provider's interface making a certain kind of honesty cheap, and the connector taking it. Where a provider gives you per-recipient detail, keep it. Where it does not, do not invent it.

One failure costs one send

The loop's error handling has a detail that is easy to write the wrong way and produces a much worse failure.

When a request fails at the transport level — a refused connection, a timeout, a network blip — the failure is caught, recorded against that recipient, and the loop moves on to the next.

Written the other way, with one guard around the whole loop, a single unreachable moment on the second of eight recipients abandons the remaining six. The count would show one sent and one failed, and six people would simply never be considered — with nothing in the record to say so.

The narrow guard costs a few characters. It is the same shape as the notification fan-out, where each destination is guarded on its own so a jammed door does not block the corridor, and it recurs in this codebase because it recurs in the problem.

Two errors joined, and the reason they are

All the collected failures are joined into one reported message rather than reported as a list.

That is a compromise with the shape of the surrounding system: the result travels back through a settings screen and a log line, both of which take a string. A structured list would be better and would need every consumer to understand it.

What matters is that the information survives to the place a person reads. A joined string containing two numbers and two reasons answers the question. A count would not, and neither would the last reason on its own.

It is worth being clear that this is a trade rather than an ideal. The right amount of structure is however much survives to the reader, and on this path a string does.

And an empty list is still not a success

The overall verdict requires two things: at least one message actually sent, and no failures recorded.

The first half is the one that catches the quiet case. A connector configured, enabled and pointed at an empty recipient list produces no sends and no failures — and a verdict testing only for the absence of errors would call that a success.

It is the same rule as the other fan-out connectors, and it is worth stating every time because it is the failure mode that survives longest. Nothing errors. Nothing arrives. There is no evidence, only an absence, and absences are not what anybody monitors.

The list is also cleaned before any of this — split on commas and line breaks, trimmed, empties dropped — so a trailing comma in a settings field does not become a send to nothing and an error from a provider about a malformed number.

What is in place, layer by layer

How a Twilio send reports itself

The recipient on the front of every error

Each failure is recorded as the number and then the reason, so the diagnosis names the destination rather than the batch.

Built in

All failures kept, not just the last

Every recipient that did not go is reported, because two failures with different causes are two different problems.

Built in

One request per recipient

Each destination is its own send, so a bad number cannot take the rest of the list with it.

Built in

Transport failures caught per send

A refused connection costs one recipient and the loop continues, rather than abandoning everybody after the failure.

Built in

The provider's own message extracted

Twilio's human-readable field is read where present, with the raw response as a fallback, so the reason is the one in their documentation.

Built in

A strict verdict with a floor

Success requires at least one send and no failures, so an empty recipient list is reported rather than passing as fine.

Built in

The list cleaned before use

Split on commas and line breaks, trimmed, empties dropped — so a trailing comma is not an attempt to message nothing.

Built in

A bounded wait per recipient

Each request is individually capped, so a slow response part-way down a list does not stall the sends behind it.

Built in

The other two SMS providers take a whole list in one call, so this level of per-recipient detail is available here and not there. The connector keeps what its provider offers rather than inventing what it does not.

Three positions held on purpose

  • Context is kept at the moment of failure rather than reconstructed afterwards. Which recipient failed is free to record and impossible to recover, and the default is to lose it because losing it requires writing nothing.
  • Per-recipient detail is kept where the provider offers it and not invented where it does not. A connector that reported per-number reasons on a provider that returns one summary would be presenting a guess as a fact.
  • A send to nobody is a failure. Requiring at least one actual send in the verdict is what turns an empty recipient list from a silent success into something a person sees.

Five questions about how failures are reported

Which recipient failed?

A good answer sounds like

It says.

What ours actually is

Every error carries its number on the front, and all of them are reported.

What if several fail for different reasons?

A good answer sounds like

All the reasons.

What ours actually is

Each is kept with its recipient rather than collapsed to the most recent one.

Does one network failure stop the rest?

A good answer sounds like

No.

What ours actually is

Caught per send, so a refused connection costs that recipient and the loop continues.

Is an empty recipient list a success?

A good answer sounds like

No.

What ours actually is

Success requires at least one actual send, not merely the absence of errors.

Where does the reason text come from?

A good answer sounds like

The provider.

What ours actually is

Twilio's own message field, with the raw response as a fallback.

Our take

Almost every hard-to-diagnose production problem is a place where the system knew something at the moment it mattered and did not write it down. The failing record's identifier, the recipient, the parameter the provider objected to — all of it is free to keep at the point of failure and unrecoverable an hour later. The discipline is small and it has to be applied at the time: when you are writing the line that records a failure, ask what a person reading it at nine on a Monday will need to know, and put that in. It is never more than a few characters, and it is the whole difference between a support conversation and an investigation.

Alerts that can tell you who did not get them

A notification system is only as good as its account of what did not arrive. That account is written at the moment of failure or not at all.

Talk through SMS alerts

Frequently asked questions

If one number is wrong, do the others still receive the message?

Yes. Each recipient is a separate send, so a bad number affects only itself. The result reports which number failed and why, alongside the count that went.

Why does the report show several errors joined together?

Because several recipients failed and each reason is kept. The result travels through a settings screen and a log line, both of which take text, so the failures are joined rather than structured — the aim is that the information survives to the person reading it.

Do the other SMS providers report per-recipient errors?

They report differently, because they work differently — the other two take a whole list in a single call rather than one request per recipient. The connectors keep the detail their provider actually offers rather than presenting a guess as a fact.

What does a Twilio error usually mean?

Most often an unreachable or malformed number, a sender not permitted to message that destination, or an account limit. Twilio's own message names which, and it is passed through unchanged so it matches their documentation.

Will failed recipients be retried?

Where the send runs as background work the usual retry rules apply to the send as a whole. The bias is toward reporting clearly rather than retrying hard — the record in your workspace is the authority, and a permanently bad number is not improved by attempting it three more times.

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center