Module kit / Messaging, email and SMS
Telegram System Alerts
messaging-email-sms/telegram-alerts
| Kind | backend |
|---|---|
| Domain | messaging-email-sms |
| Maturity | prod |
| Reusable | extract |
| Stack | fastapi-0.118, sqlalchemy-2.0-sync, pydantic-settings-2.11, httpx-0.28, apscheduler-3.11 |
| Depends on | _shared/fastapi-project-layout, _shared/scheduler-and-background-work |
| Frontends | messaging-email-sms/ui/email-template-editor |
| Reference implementation |
|
| Measured on | alchemy_labs main 62ba4fc41009b454e1c033e5036c7e8577db2061 2026-09-12 |
| Version | 1 |
Purpose
When something breaks, TelegramService.send_message/send_message_sync pushes one HTML-formatted message to a global ops chat via the Telegram Bot API's sendMessage, and send_to_chat/send_form_submission_notification pushes one to a per-workspace chat_id when a hosted form gets a submission - both paths exist so an engineer or a workspace owner learns about a crash, a failed deploy check, a monitoring probe, or a new lead without watching a dashboard (module docstring, telegram_service.py 1-6). The invariant that makes it trustworthy is narrower than "alerting": it is success-only-silent by construction (no code path in this file ever fires on a good outcome, docstring line 5, "Only sends alerts when things FAIL"), and it never raises and never blocks its caller - every guard, network error and non-200 response is caught and turned into a logged bool, so a dead bot token degrades to silence, never to a 500 on the request that triggered it (send_message 70-92, send_message_sync 109-131, send_to_chat 340-362).
When to use
Read it before touching send_message, send_message_sync, send_to_chat, any format_*_alert method, send_form_submission_notification, send_deploy_failure, send_monitoring_failure, or the TELEGRAM_*/FORM_SAVE_ALERTS_ENABLED/MONITORING_* settings; before adding a new failure path that should page someone; when someone asks "why didn't we get pinged", "why did we get paged twice", "is the ops bot even configured", or "which chat does a form submission notify". Trigger phrases: "telegram alert", "ops chat", "sendMessage", "TELEGRAM_BOT_TOKEN", "TELEGRAM_ALERTS_ENABLED", "telegram_chat_id", "deploy failure alert", "monitoring failure alert", "form submission telegram".
Not this recipe: services/accounting/telegram_bot_service.py (telegram_bot_service) and services/service_desk/telegram_bot.py (desk_telegram_bot) - two separate long-polling Telegram bots (their own ACCOUNTING_TELEGRAM_BOT_TOKEN/DESK_TELEGRAM_BOT_TOKEN, both SecretStr, core/config.py 1041, 1047) that receive updates via getUpdates//start <code> pairing for receipt uploads and Service Desk replies respectively (scheduled_jobs.py 485-498); this recipe is exactly the opposite shape, an outbound-only sendMessage wrapper with no receive path and no pairing state. Also not this recipe: POST /api/admin/notify's own body-composition and email branch (admin_router.py 2919-3060) beyond the Telegram half it duplicates, see Gotchas; the workspace_settings table itself, which this recipe only reads one key out of (telegram_chat_id) and does not own.
Stack assumptions
At 62ba4fc: fastapi==0.118.2, httpx==0.28.1 (a fresh AsyncClient/Client opened and closed on every single call - send_message 71, send_message_sync 110, send_to_chat 341 - there is no pooled or shared client anywhere in this file), pydantic-settings==2.11.0 for Settings (core/config.py). No SQL, no ORM import in telegram_service.py itself. Foundations assumed: S4 (fastapi-project-layout) for mount_router/app_fastapi.py wiring the middleware, admin_router and public_forms_router that call this service; S12 (scheduler-and-background-work) for the two APScheduler job families that call it on a timer - monitoring_checks on IntervalTrigger(minutes=settings.MONITORING_CHECK_INTERVAL_MINUTES) (default 5) when MONITORING_ENABLED (default True, scheduled_jobs.py 266-274, config.py 1028-1029), and the three one-off post_deploy_* jobs on DateTrigger at +5s/+5min/+60min after a deploy (scheduled_jobs.py 1560-1589).
Environment, all in core/config.py: TELEGRAM_BOT_TOKEN (1035, Optional[str], default None - not SecretStr, unlike the two sibling bot tokens noted above, see Gotchas), TELEGRAM_CHAT_ID (1036, Optional[str], default None), TELEGRAM_ALERTS_ENABLED (1037, bool, default False), FORM_SAVE_ALERTS_ENABLED (1038, bool, default True), MONITORING_ENABLED (1028, bool, default True), MONITORING_CHECK_INTERVAL_MINUTES (1029, int, default 5). Fail behaviour: send_message/send_message_sync short-circuit False when not self.enabled (TELEGRAM_ALERTS_ENABLED) OR when bot_token/chat_id are falsy (62-68, 103-107); every network or parse exception is caught and logged, never raised (90-92, 129-131). send_to_chat (321-362, the path send_form_submission_notification uses) checks bot_token and the passed-in chat_id but never checks TELEGRAM_ALERTS_ENABLED - a workspace's form-submission notice fires even when the global ops-alert flag is off; this asymmetry is real, not an oversight documented anywhere, see Gotchas. None of the four call sites into send_message/send_message_sync (error_logging_middleware.py, form_save_alert_service.py, monitoring_service.py, post_deploy_verification.py) surfaces the returned bool as an error to its own caller - all four treat it as fire-and-forget, differing only in whether they log a line about it.
Data model
None. Confirmed by reading the whole 481-line file and every call site under Reference implementation: no table, no ORM model, no migration belongs to this recipe. What is NOT stored, deliberately, by this recipe: the alert text itself is never persisted anywhere - there is no delivery log, no sent-message table, no history of what was alerted, when, or whether Telegram actually accepted it beyond the one bool/log line at send time. The only persistence adjacent to this recipe is read-only and belongs to another recipe: the per-workspace telegram_chat_id override is read out of the pre-existing workspace_settings table's settings JSON column via raw SQL (SELECT settings FROM workspace_settings WHERE workspace_id = :wid, public_forms_router.py 820-823 and 1246-1249; table shape id uuid, workspace_id uuid, settings json, created_at, updated_at, migrations/versions/baseline_prod_schema_001.py 2229-2238) - that table and column are not owned by this recipe and carry other workspace settings too.
API surface
This recipe is a called-from, not a router - the one HTTP route in scope is an admin utility that partially overlaps it.
| Route | Auth | Tenancy | Body / Codes |
|---|---|---|---|
POST /api/admin/notify (admin_router.py 2930, mounted at /api/admin, app_fastapi.py 879) | AdminUser = Depends(get_current_admin_user) (api/dependencies.py 568) | Admin-only, no workspace scoping; telegram_chat_id in the body overrides the default TELEGRAM_CHAT_ID for that one call (2926, 2996) | NotifyRequest (2919-2927): to, subject, html, text, telegram: bool = False, telegram_chat_id, telegram_parse_mode: str = "HTML". 400 if neither to+subject (email) nor telegram=true is requested (2962-2966), or if neither html nor text is given (2968-2972); 502 if every requested channel failed (3041-3045); 200 otherwise with a per-channel result (3052-3060). This route builds its own inline httpx.Client().post(...) to sendMessage (3001-3019) instead of calling telegram_service.send_message/send_to_chat - it duplicates the API call rather than reusing this recipe's service, see Gotchas. |
Everything else is a Python function surface, not a route:
| Function | Called from | Returns |
|---|---|---|
send_message(message, parse_mode="HTML", disable_notification=False) (45-92, async) / send_message_sync(...) (94-131, sync, identical logic) | error_logging_middleware.py 281 - asyncio.create_task(...), never awaited; form_save_alert_service.send_alert 93 - awaited, result discarded; monitoring_service._send_telegram_alert_if_failures via send_monitoring_failure 620 - awaited, result discarded; post_deploy_verification.py _send_telegram_failure_alert 418 - sync call, result only used for a log line (419-422) | bool, never raises |
send_to_chat(chat_id, message, parse_mode="HTML", disable_notification=False) (321-362, async) | send_form_submission_notification 443 | bool, never raises |
send_form_submission_notification(chat_id, form_name, data, tracking_data=None) (430-443) | public_forms_router.py 828 and 1254, both awaited inside a try/except that only logs a warning on failure (804-805/834-835, 1231-1232/1260-1261) | bool |
send_deploy_failure(...) (445-461) | nobody - zero callers anywhere in this repo, confirmed by grep; post_deploy_verification.py builds the equivalent message itself via format_deploy_failure_alert + send_message_sync instead (394-422) rather than calling this method, see Gotchas | bool |
send_monitoring_failure(...) (463-477) | monitoring_service._send_telegram_alert_if_failures 620, the only caller | bool |
Application logic
send_message/send_message_sync, identical steps, one async/httpx.AsyncClient, one sync/httpx.Client (45-92, 94-131):
- Enabled guard:
if not self.enabled: return False(62-64/103-104) -self.enabledisTELEGRAM_ALERTS_ENABLED, read once atTelegramService.__init__(29-32), not re-read per call. - Config guard:
if not self.bot_token or not self.chat_id: return False(66-68/106-107). - Build and POST:
json={"chat_id": self.chat_id, "text": message, "parse_mode": parse_mode, "disable_notification": disable_notification}tohttps://api.telegram.org/bot{bot_token}/sendMessage,timeout=10.0(72-81/111-120). - Interpret the response:
status_code == 200logs"Telegram alert sent successfully"and returnsTrue(83-85/122-124); anything else logsf"Telegram API error: {response.status_code} - {response.text}"and returnsFalse(86-88/125-127) - the rawresponse.textis logged verbatim, including whatever Telegram's error body contains. - Catch-all: any exception (timeout, DNS, connection refused) is caught, logged with the message-less
f"Failed to send Telegram message: {e}", and turned intoFalse(90-92/129-131).
send_to_chat (321-362) is the same shape with two differences: it takes an explicit chat_id argument instead of self.chat_id, and it does not check self.enabled at all (guards are only bot_token at 332-334 and the passed-in chat_id at 336-338).
Side-effect ordering at each real call site (none of these write anything through this recipe; the ordering that matters is relative to the caller's own state):
error_logging_middleware.py(240-283): the crash'sSystemLogrow is built,db.add'd,db.commit()'d anddb.refresh()'d (240-262) before the Telegram branch runs at all (266); the alert is then fired viaasyncio.create_task(telegram_service.send_message(alert_msg))(280-281) and the enclosing function returns without awaiting it - so the crash log's durability never depends on Telegram, and a process exit right afterreturn log_entry.idcan drop the alert with no record that it was ever attempted.form_save_alert_service.send_alert(61-96): an in-process, in-memory cooldown check (_should_alert, 48-55, a plaindict[str, datetime]keyed bymd5(endpoint:error_type), 30-minute window) runs before the send; there is no database backing this dedup, so a process restart resets every cooldown to zero.monitoring_service._send_telegram_alert_if_failures(584-627): called at the tail ofrun_all_checks, after every sub-check has already written its ownAlertTyperow viaself._send_alert(...)(562-572) - the Telegram push reports on persisted alert state, it does not gate it.post_deploy_verification.py_send_telegram_failure_alert: called fromprint_summary()after the pass/fail/rollback decision is already made and printed (353-387) - the alert is purely a report of a decision the script already took, it never influences whether rollback fires.public_forms_router.py(both/submitat 807-835 and/submit-aiat 1234-1261): theFormSubmissionrow isdb.commit()'d earlier in the handler (measured at line 727 for/submit) before the email and Telegram best-effort blocks run in sequence, each independently wrapped in its owntry/exceptthat only logs a warning - a Telegram failure here can never roll back or block the already-committed submission, and an email failure does not prevent the Telegram attempt from running next.
Invariants
- No code path in this file sends on success; every entry point is reached only from a failure, a crash, or a new inbound submission (module docstring 3-6; no test in this repo exercises a success-path call, matching the codebase).
send_message/send_message_sync/send_to_chatnever raise: every branch returnsbool(verified by reading all three functions end to end - each has exactly onetry/except Exceptionwrapping the entire network call).- A caller's own persistence never depends on Telegram succeeding: in every one of the five call sites above, the caller's own state (a
SystemLogrow, anAlertTyperow, aFormSubmissionrow, a printed exit-code decision) is established before or independently of the Telegram call, and a Telegram failure is always caught locally and only logged. send_to_chat/the per-workspace form-notification path is reachable regardless ofTELEGRAM_ALERTS_ENABLED(Stack assumptions) - this is a real, undocumented asymmetry withsend_message, not a bug fixed at this commit.
Frontend
No frontend module in this repo renders or displays a Telegram alert - the message lands in Telegram itself, outside Labs' own UI, so there is nothing here to render in the sense the template means. The frontend_modules entry this recipe is required to carry:
messaging-email-sms/ui/email-template-editor does NOT render Telegram alerts; it is the nearest
domain sibling with a frontend surface (the email
side of this domain's messaging transports). Stated
plainly: this recipe has no frontend of its own.
Minimum admin/debug surface: POST /api/admin/notify (API surface, above) is the only UI-adjacent surface, and it is an admin API endpoint with no dedicated screen in this repo - an admin drives it directly (curl, an internal script) rather than through a Labs page. There is no viewer anywhere for what was already sent, because nothing is persisted (Data model).
Security & tenancy
Auth dependency: TelegramService itself has none - it is called from already-authorized or already-server-side-decided contexts, never from a request body an anonymous caller controls end to end. The one authenticated entry point is POST /api/admin/notify (AdminUser, admin-only). The one public, unauthenticated entry point that reaches this recipe is POST /f/{embed_code}/submit//submit-ai, mounted surface_exempt("public", "form embeds on client sites - no auth by design") (app_fastapi.py 949) - but the chat_id it notifies is never taken from the submitter: it is resolved server-side from template.workspace_id (falling back to a SELECT ... FROM workspaces WHERE owner_id = ... lookup, 811-817) and then from that workspace's own workspace_settings.telegram_chat_id, so an anonymous form submitter cannot choose or redirect which chat gets pinged.
Fail direction: closed, not open - a missing token, a missing chat id, or TELEGRAM_ALERTS_ENABLED=false all silently no-op (Stack assumptions) rather than raising or blocking the caller; but "closed" here means silent total loss, not "safely retried" - there is no queue, no delivery log, and no retry anywhere in this file (Data model), so a misconfigured bot fails every alert forever with no record that anything was ever attempted, until someone re-reads the logs by hand.
Secrets at rest: TELEGRAM_BOT_TOKEN is declared Optional[str], not SecretStr (core/config.py 1035) - unlike its two sibling bot tokens in the same file, ACCOUNTING_TELEGRAM_BOT_TOKEN (1041) and DESK_TELEGRAM_BOT_TOKEN (1047), both SecretStr. The token is interpolated directly into the request URL path on every call (_get_api_url, 34-36, f"https://api.telegram.org/bot{self.bot_token}/{method}"), so anything that logs an outgoing request URL (an httpx debug logger, an APM, a proxy access log) would capture it in plaintext; nothing in this file itself logs the URL, but nothing masks the token if a Settings object is ever repr'd or dumped either, which a SecretStr field would prevent. Rate limits: none, anywhere in this recipe, for the primary crash-alert path - error_logging_middleware.py's per-crash asyncio.create_task has no cooldown or coalescing at all, so a crash loop sends one Telegram message per failing request; form_save_alert_service is the only call site with a cooldown (30 minutes, in-memory, per endpoint+error-type, item 2 under Application logic) but it has zero callers in this repo (Gotchas), so that cooldown is presently unexercised in production. Attacker-controlled inputs: form field values reach format_form_submission_alert and are HTML-embedded into the Telegram message with a [:200] truncation per field (364-403) but no HTML-escaping - _escape_markdown (38-43) exists in this file but is never called from format_form_submission_alert or any of the other format_* methods (confirmed by grep: _escape_markdown has zero callers anywhere in this file or its callers); a form field containing Telegram HTML markup (<b>, <a href=...>) would be interpreted by Telegram's parse_mode="HTML" renderer rather than shown literally.
Tests
No dedicated test file exists for telegram_service.py or the TelegramService class itself - confirmed by find backend/tests -iname "*telegram*", which returns only test_desk_telegram_reply.py (the unrelated Service Desk bot, Not this recipe) and a stale .pyc. None of format_failure_alert, format_deploy_failure_alert, format_monitoring_failure_alert, format_form_submission_alert, _escape_markdown, send_to_chat, or send_message_sync is referenced by name in any test file in this repo (grepped for each, zero hits in backend/tests). This is a genuine gap, not an oversight this recipe can paper over: the real sendMessage HTTP call, the message-formatting methods, and the two config guards are all currently unexercised by any test, in either direction.
What the suite exercises instead, always by mocking the service boundary rather than calling into it:
backend/tests/v2/test_f3_credential_logging.py- testsErrorLoggingMiddleware, notTelegramService; every test that reaches the Telegram branch patchesservices.telegram_service.telegram_service.send_message(e.g.test_log_exception_called_once140-167,test_telegram_payload_excludes_phone215-234) and asserts on the middleware's side: exactly one alert per crash (proving the double-await fix, see Gotchas), and thatuser_phonenever appears in the message string handed tosend_message. It never asserts whatsend_messageitself does with that string.backend/tests/v2/coverage/test_forms_g11_public_forms_router_endpoints_protection_router_testclient_no_auth_public_flow_.py- thepatched_submit_collaboratorsfixture (256-272) patchesr.telegram_service.send_form_submission_notificationto anAsyncMock()specifically to neutralize it before testing/f/{embed_code}/submit; no test in this file un-mocks it to check the Telegram call is actually attempted or what chat id it targets.
Hermetic vs live: every test that touches this recipe is hermetic by construction (the network call is mocked out before it can happen); nothing in this repo exercises a real or fake Telegram API response.
Deploy & ops
Config keys: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_ALERTS_ENABLED, FORM_SAVE_ALERTS_ENABLED, MONITORING_ENABLED, MONITORING_CHECK_INTERVAL_MINUTES (all core/config.py, cited above). No migration belongs to this recipe (Data model). Scheduler: monitoring_checks on a 5-minute IntervalTrigger when MONITORING_ENABLED (scheduled_jobs.py 266-274); the three post_deploy_* jobs on one-shot DateTriggers at +5s/+5min/+60min after a deploy, registered by whatever calls the (unread in this recipe) scheduling function around scheduled_jobs.py 1555-1589; run_post_deploy_checks (1488-1538) itself does not call telegram_service directly, it delegates entirely to monitoring_service.run_all_checks, which is where the actual alert fires (1531 comment: "Telegram alert is sent by monitoring_service.run_all_checks").
Argus-style checks: none exist for this recipe specifically. The closest measured signal is post_deploy_verification.py's own summary line, " (Telegram alerts disabled)" (406), printed whenever telegram_service.enabled is False at deploy-verification time - a human watching deploy output sees this line if the ops channel is not wired up, but nothing automated checks for it. Rollback: nothing deployable beyond the code itself; there is no data to roll back (Data model).
Gotchas
- 2026-06-05 (
6f3e884,error_logging_middleware.py, still visible in the file's own comment at line 169):_log_exceptionused to be awaited twice indispatch(), producing twoSystemLogrows and two Telegram alerts per crash; also,user_phoneused to be included in the Telegram alert payload (PII leak, now dropped, comment at line 196). Both are the two thingstest_f3_credential_logging.pyexists to pin down (test_log_exception_called_once,test_telegram_payload_excludes_phone). - 2026-09-12 (measured at 62ba4fc):
send_to_chat/send_form_submission_notificationnever checkTELEGRAM_ALERTS_ENABLED(Stack assumptions, Application logic) - a workspace's own form-submission Telegram notice keeps firing even with the global ops-alert switch off. Nothing in the code or commit history documents this as intentional; it reads as an inconsistency between the two send paths rather than a designed distinction. - 2026-09-12 (measured at 62ba4fc):
form_save_alert_service.py'sform_save_alert_service/send_alerthas zero callers anywhere in this repo outside its own file (grep -rn "form_save_alert_service\b" backend --include="*.py"matches only the file's own definition). The entire "form save failure" alert channel its module docstring describes ("Sends Telegram alerts when form save endpoints fail... Helps catch schema issues like missing database columns immediately") is dead: nothing wiresFORM_SAVE_ENDPOINTSorsend_alertinto any actual request path, error handler, or middleware.FORM_SAVE_ALERTS_ENABLEDdefaultsTrueand controls a code path nothing ever reaches. - 2026-09-12 (measured at 62ba4fc):
send_deploy_failure(telegram_service.py445-461) has zero callers anywhere in this repo (API surface, above);post_deploy_verification.pyreimplements the sameformat_deploy_failure_alert+ send call manually instead (394-422) rather than calling this method. Similarly,POST /api/admin/notifybuilds its own inlinehttpxPOST tosendMessage(admin_router.py3001-3019) instead of callingsend_message/send_to_chat- a third, independent implementation of the same three-line HTTP call exists in this codebase, all three able to drift out of sync (e.g. onlytelegram_service.py's two methods setdisable_notificationat all). - 2026-09-12 (measured at 62ba4fc):
_escape_markdown(38-43) is dead code - defined, never called (grepped across the whole file and every caller module). Everyformat_*_alertmethod builds HTML strings with raw f-string interpolation and no escaping (Security & tenancy);_escape_markdown's MarkdownV2 escaping would not even be the right function for the HTMLparse_modeevery call site actually uses. - 2026-09-12 (measured at 62ba4fc):
TELEGRAM_BOT_TOKENisOptional[str], notSecretStr, unlikeACCOUNTING_TELEGRAM_BOT_TOKEN/DESK_TELEGRAM_BOT_TOKENin the same settings file (Security & tenancy). A rebuild that copies theSecretStrpattern from the other two Telegram bots would close this gap for free.
Reference implementation
Paths relative to the repo root; all at main 62ba4fc41009b454e1c033e5036c7e8577db2061 unless noted.
alchemy_labs/backend/services/telegram_service.py(481 lines, whole file in scope): module docstring (1-6);TelegramService.__init__/_get_api_url/_escape_markdown(29-43);send_message/send_message_sync(45-131);format_failure_alert(133-180, unused by any call site under Reference implementation - no caller found by grep, a fourth unused formatter beyond the three named in Gotchas);format_deploy_failure_alert(182-237);format_monitoring_failure_alert(239-319);send_to_chat(321-362);format_form_submission_alert(364-428);send_form_submission_notification(430-443);send_deploy_failure(445-461);send_monitoring_failure(463-477); module-level singletontelegram_service(481).alchemy_labs/backend/middleware/error_logging_middleware.py: the F3 fix comments (169, 196); the Telegram branch (266-283).alchemy_labs/backend/services/form_save_alert_service.py(99 lines, whole file in scope, dead code per Gotchas):FORM_SAVE_ENDPOINTS(19-30);FormSaveAlertService(33-99).alchemy_labs/backend/services/monitoring_service.py:_send_telegram_alert_if_failures(584-627) and its caller (574-580).alchemy_labs/backend/scripts/post_deploy_verification.py:print_summary(334-392);_send_telegram_failure_alert(394-427).alchemy_labs/backend/api/routers/admin_router.py:NotifyRequest(2919-2927);admin_notify(2930-3060).alchemy_labs/backend/api/routers/public_forms_router.py: import (42);/submit's Telegram block (807-835);/submit-ai's Telegram block (1234-1261).alchemy_labs/backend/services/scheduled_jobs.py:run_monitoring_checksregistration (266-274);run_post_deploy_checks(1488-1538); the threepost_deploy_*DateTriggerregistrations (1560-1589).alchemy_labs/backend/core/config.py:MONITORING_ENABLED/MONITORING_CHECK_INTERVAL_MINUTES(1028-1029);TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID/TELEGRAM_ALERTS_ENABLED(1035-1037);FORM_SAVE_ALERTS_ENABLED(1038);ACCOUNTING_TELEGRAM_BOT_TOKEN(1041);DESK_TELEGRAM_BOT_TOKEN/DESK_TELEGRAM_BOT_USERNAME(1047-1048);TELEGRAM_ALCHEMY_GROUP_CHAT(1071, a third, unrelated hardcoded chat id for LinkedIn calendar pings - not this recipe).- Tests:
alchemy_labs/backend/tests/v2/test_f3_credential_logging.py(292 lines, whole file in scope);alchemy_labs/backend/tests/v2/coverage/test_forms_g11_public_forms_router_endpoints_protection_router_testclient_no_auth_public_flow_.py, thepatched_submit_collaboratorsfixture (256-272).
Deviations of the reference from an ideal implementation, all dated 2026-09-12: no dedicated test exists for TelegramService's own methods (Tests); form_save_alert_service.py and send_deploy_failure are both dead code with zero callers; the same sendMessage HTTP call is implemented three separate times in this codebase (telegram_service.py, admin_router.py, and implicitly re-derived by any future call site that copies one of them) instead of once; _escape_markdown is dead code that would be the wrong escaping function for the parse_mode every caller actually uses; TELEGRAM_BOT_TOKEN is not SecretStr unlike its two sibling bot tokens; send_to_chat does not honor TELEGRAM_ALERTS_ENABLED.
Acceptance checks
Read-only checks against the alchemy_labs checkout at 62ba4fc, run from the repo root. Expected observations are exact, as measured 2026-09-12.
sed -n '34,36p' backend/services/telegram_service.pyshows_get_api_urlbuildingf"https://api.telegram.org/bot{self.bot_token}/{method}".grep -n "self.enabled" backend/services/telegram_service.pyprints exactly three lines: the assignment in__init__(32) and the two guard checks insend_message/send_message_sync(62, 103) -send_to_chat(321-362) contains no reference toself.enabledat all, confirming it never checks the flag (Stack assumptions).grep -rn "form_save_alert_service\b" backend --include="*.py" | grep -v "form_save_alert_service.py:"prints nothing - confirms the dead-code finding in Gotchas.grep -rn "send_deploy_failure\b" backend --include="*.py"prints exactly one line, the method's owndef(445) - confirms zero callers.grep -n "_escape_markdown" backend/services/telegram_service.pyprints exactly one line, thedefitself (38) - no method in the file ever calls it.grep -rn "_escape_markdown" backend --include="*.py" | grep -v telegram_service.pyprints nothing - confirms zero callers anywhere in the repo.grep -n "TELEGRAM_BOT_TOKEN\|ACCOUNTING_TELEGRAM_BOT_TOKEN\|DESK_TELEGRAM_BOT_TOKEN" backend/core/config.pyshowsTELEGRAM_BOT_TOKENtypedOptional[str](1035) against the other two typedOptional[SecretStr](1041, 1047).find backend/tests -iname "*telegram*"lists onlytest_desk_telegram_reply.py(and its.pyc) - no test file targetstelegram_service.pyby name.git log --oneline -- backend/services/telegram_service.pyprints four commits (d54b3f8,0705aea,7719097,4d01672);git log -1 --format=%ad --date=short 6f3e884(the F3 fix touching the middleware, not this file directly) prints2026-06-05.
Content synced from the prima-modules repository at 0df2ec8 on 2026-09-14. The recipe file is the source; this page is a rendering of it.