AWRA OpsHub Search

Sent Two, Failed One

Five of the six chat connectors decide a message was delivered by looking at the response status. The sixth cannot, because its provider will answer two hundred and then say no inside the document — and a connector that trusts the status alone counts that as sent.

Integrations & Data AWRA OpsHub Team 11 min read

Almost every integration in the world has the same three lines in it. Make the request. Check whether the status was in the successful range. Report accordingly. It is correct for most providers, it is what every example in every tutorial does, and there is a specific family of APIs for which it is exactly wrong — where the transport succeeded, the response arrived, the status is fine, and the body politely explains that nothing happened.

The healthy response that means no

Telegram's Bot API answers with a document that carries its own success flag. When the flag is true, the message went. When it is false, there is a description saying why — and the status code beside it can still be the ordinary successful one, because from the transport's point of view nothing went wrong. A request was made. A response came back. Everything is fine.

The reasons it says no are the ordinary operational ones, not exotic failures:

  • The bot was removed from the group by somebody tidying up members.
  • The group was upgraded to a supergroup, which changes its identifier.
  • A person in the recipient list blocked the bot, or never started a conversation with it.
  • The chat identifier was mistyped when it was configured, and has never once worked.

Every one of those is a destination nobody is watching. That is what makes counting them as delivered so expensive.

So this connector requires both: a successful status and the flag inside the response. It is the only one of the six that reads the body to decide, and it is the only one that has to.

The status is not the answer here. The provider says so in the document it returns.
The status is not the answer here. The provider says so in the document it returns.

Why not check the body everywhere, to be safe?

Because a check that does not match its provider's contract is worse than no check.

One of these providers answers success with no body at all. Another may answer with an empty body or the single digit one. A third returns a representation of the message it just posted. Writing a shared routine that looks for a success flag would find nothing in any of those and would have to treat not found as fine — at which point it is doing nothing, expensively, while looking like a safeguard.

A defensive check that cannot fire is worse than an absent one, because the absent one does not make anybody feel covered. The honest arrangement is per-provider: read what their success actually claims, and write the test to that.

And then the counting

Telegram sends to a list of chats rather than to a single place, so each destination is a separate request and each can fail on its own. The result carries three things: how many landed, how many did not, and the reason the last failure gave.

The verdict is strict — everything landed and nothing failed. Two of three is reported as a failure, with the count that says it was two of three.

That combination is what makes a partial delivery diagnosable rather than merely alarming. The count tells you the shape of the problem and the message tells you the cause.

What you see What it almost always means Where to look
One failed of five Something about that one chat The reason names it — removed, blocked, unknown
All failed The credential or the connection The token, or whether the network can reach the provider
None sent, none failed The recipient list is empty The configuration — nothing was attempted
All sent It worked Nothing

The third row deserves its own mention because it is the one most systems get wrong. A configured, enabled connector with nobody to send to reaches nobody, and reporting that as success is how a channel goes quiet for six weeks without a single error anywhere.

What happens to the reason

The description the provider supplies is carried back as written, not paraphrased. It is worth showing why that matters with the actual phrases.

A missing chat, a bot that was removed from a group, a group that has been upgraded and now has a different identifier — these arrive as short, specific sentences that name the condition. Each one points at a different fix, and each is a fix somebody can do in about a minute once they know which it is.

A house translation would collapse all three into something like could not deliver the message, which is true, useless, and costs whoever reads it the twenty minutes of guessing that the provider had already saved them.

The exception that is caught, and why it counts as a failure

One more layer, easy to skip. A send can fail before there is any response at all — a DNS problem, a refused connection, a timeout.

That is caught per recipient rather than around the whole loop. The distinction is the entire point: caught around the loop, one unreachable moment abandons every remaining recipient. Caught per recipient, it costs that one send and the rest of the list carries on.

It is counted as a failure and its message becomes the reported reason, so a transport problem is visible as a transport problem rather than as a silent shortfall in the sent count.

What is in place, layer by layer

How a Telegram send is judged

Status and body, both required

Success needs a successful response and the provider's own flag inside it, because this API can answer healthily and still decline.

Built in

A per-provider test, not a shared one

The five providers whose status is the answer are judged on the status. Imposing one rule on six contracts would be wrong somewhere every time.

Built in

One request per chat

Each destination is its own send, so one removed bot cannot cost the rest of the list its notification.

Built in

Counted results

How many landed and how many did not, so a partial delivery reads as partial rather than as a single verdict either way.

Built in

A strict verdict

Anything short of all of them is a failure, because a partial delivery on an approval means somebody who needed to know does not.

Built in

An empty list is not a success

A live connector with nobody configured is reported as a failure, because reaching nobody quietly is the condition worth surfacing.

Built in

The provider's description, verbatim

Short specific reasons that name the condition, carried back as written rather than flattened into a house phrase.

Built in

Transport errors caught per recipient

A connection failure costs one send rather than abandoning the rest of the loop, and is counted and reported as a failure.

Built in

A bounded wait per send

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

Built in

The same fan-out accounting is used by the one other connector that sends to a list rather than a place. The four that post to a single channel return one accepted-or-refused result, because there is nothing to count.

Three positions held on purpose

  • Success is defined by each provider's contract rather than by a house rule. A shared success test across six APIs is right on most and silently wrong on the one that can refuse inside a healthy response — which is the one where being wrong costs the most.
  • A defensive check is not added where it cannot fire. Looking for a success flag in a provider that returns no body would mean treating its absence as fine, which is a safeguard that does nothing while making everybody feel covered.
  • Failures are caught around each send rather than around the batch. The narrower catch is a handful of extra characters and the difference between one lost notification and all of them.

Five questions about how delivery is decided

Can this provider succeed and refuse at once?

A good answer sounds like

They know which do.

What ours actually is

Telegram can. Its connector requires the status and the flag; the other five are judged on the status.

What if two of five fail?

A good answer sounds like

Counted and reported.

What ours actually is

Three sent, two failed, and the provider's reason for the failure.

What if nobody is configured?

A good answer sounds like

That is a failure.

What ours actually is

Reported as one, rather than as a successful send to nobody.

What does a failure message say?

A good answer sounds like

The provider's own words.

What ours actually is

A short specific reason naming the condition, carried back unchanged.

Does one unreachable moment stop the batch?

A good answer sounds like

No.

What ours actually is

Transport errors are caught per recipient, so the rest of the list still goes.

Our take

If there is one habit worth taking from this whole connector series, it is this: read what a provider means by success before you write the line that checks it. It takes about twenty minutes per provider, it is the least interesting work in the job, and it is the difference between an integration that tells you the truth and one that reports perfect health into a channel nobody has received a message in since March. Nothing about the wrong version looks wrong. That is precisely the problem — the counter goes up, the status stays green, and the only evidence is an absence that nobody is positioned to notice.

Find out whether your alerts are arriving

The useful test is not whether the integration reports success. It is whether anyone can point at a message that arrived this week.

Talk through operational alerts

Frequently asked questions

We get "chat not found" — what does that mean?

Usually one of two things: the identifier was mistyped when configured, or the group was upgraded to a supergroup, which changes its identifier. Both are quick fixes once you know which, and the message points straight at the configured chat rather than at the connector.

Why is the whole send reported as failed when most of it worked?

Because a partial delivery on an operational alert means somebody who needed to know does not, and a green tick over that is a false statement about who has been told. The count beside the verdict is what makes it actionable — one failure of five is a chat problem, five of five is a credential problem.

Only the last error is reported — what about the others?

When several recipients fail it is almost always for one shared reason, so every message is the same. Where the causes genuinely differ, the count is what tells you so: one failure out of eight is individual, eight out of eight is systemic. The count carries the diagnosis and the message carries the detail.

Does a failed notification get 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 — these are notifications, the record in your workspace is the authority, and a duplicate alert costs a glance while a silent failure costs a decision.

How do we know the connector is healthy without waiting for an event?

The connection test sends a real message down the same path a notification takes, to the same configured chats, and reports the same counts. If it says three sent and none failed, all three destinations are currently reachable with the current credential.

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