Limits and errors
Two things decide whether an error is useful: whether it says what to do, and whether it is safe to log. Both are handled here rather than passed on.
Copy this
export default defineEventHandler(async () => {
try {
await telegram.send('x'.repeat(5000))
} catch (error) {
return { failed: (error as Error).message }
}
})
That runs, and it answers:
Telegram rejects messages over 4096 characters, got 5000
Nothing was sent. The count happened here, so you get a number instead of an API error after a round trip.
No error carries a token
For Telegram the token sits in the url. For Discord and Slack the webhook url is the credential. A raw fetch error puts the full url in its message, and from there it goes into your log, your error tracker and your ticket.
So the errors are rebuilt:
Discord responded 401
Telegram sendMessage failed: chat not found
Slack chat.postMessage failed: missing_scope, needs the scope `chat:write`
The target is named, the address never is.
cause, so nothing is lost:catch (error) {
console.error(error.message) // safe to log
console.error(error.cause?.status) // 401
console.error(error.cause?.data) // what the service actually said
}
What is checked before sending
| Check | Channel |
|---|---|
| Text length | all, each with its own counting |
| Caption length, 1024 | Telegram, when there is an image |
| Grapheme and byte count | Bluesky |
| Attachment count | Discord 10, Bluesky 4, Mastodon instance dependent |
| File size | Discord 10 MB, Bluesky about 1 MB |
| Missing credentials | all, naming the variable |
A missing variable says which one:
Telegram bot token is not defined. Set PIGEON_TELEGRAM_BOT_TOKEN
Services that fail with a 200
Two of them, and both would otherwise pass for success:
Slack answers 200 with { ok: false, error: 'missing_scope' }. The module throws,
and names the scope when Slack says which one is needed.
Bluesky accepts a putRecord on a post with a 200 and then ignores it. There is
nothing to throw on, so the channel does not offer edit at all.
Refusals that are not errors
Where a service cannot do something, the method is missing from the type, so it is a red line while typing rather than a surprise in production:
ntfy.edit(message, 'new')
// ^^^^ Property 'edit' does not exist
Called anyway from plain JavaScript, it explains itself instead of failing as
undefined is not a function:
slack cannot edit through an incoming webhook: it answers with the plain text `ok`
and gives no message id, so there is nothing to address. Set PIGEON_SLACK_BOT_TOKEN,
and give the app the `chat:write` scope.
The wording separates two cases on purpose: the service cannot do it at all, or it can but needs other credentials than the ones you configured.
Warnings
One case is neither an error nor silence. Bluesky carries one embed per post, so images and a link card cannot both go. Refusing the whole post would be worse than sending a slightly reduced one, but it must not happen quietly:
[nuxt-pigeon] bluesky: a post carries one embed, so the link card wins.
Your first image became its thumbnail.
That goes to the server log. It is the only place the module writes a warning rather than throwing, and the rule behind it is: throw when we cannot send what you asked for, warn when we send it differently.
What will cost you an hour
- One dead channel must not kill the request.
Promise.allSettled, neverPromise.all, when you send to several. - Message contents are never logged. They can be personal data. Errors name the channel and the reason, not what you were sending.
- A limit can be fine before an edit and too long after it. Telegram drops from 4096 to 1024 the moment there is an image.
- Mastodon's limit is not 500. It is whatever the instance says, and the module asks once and remembers.
Read more
- Retries and rate limits
- Sending text - the limits, per channel
- Changing and removing - which channel refuses what