feat(music): notify the admin Telegram chat when a generation finishes
Row 5 of the chat#1999 matrix.
What arrives
🎵 New song generated
https://chat.recoupable.dev/music/11111111-2222-4333-8444-555555555555
Account: artist@label.com
Length: 25.9s
Prompt: Genre: lo-fi soul. BPM: 82.
Lyrics: [verse] Morning light…
The link leads. The point of the message is to go and listen, and the song page resolves for any Recoup org member, so it opens for whoever reads the chat.
Length is the real output, not the requested duration — the two now differ routinely, and the actual figure is what we are billed on.
Failures notify too, and look different
đźš« Music generation failed
…
Reason: Lyrics structure tags were rejected.
A song that failed after the caller was gated on credits is the event most worth reacting to. It reads nothing like a success when skimming, which matters in a chat you scroll.
Where it fires
From the workflow, after the audio is stored and credits are deducted — never from the request path. A Telegram outage must not fail a generation the customer already paid for.
Its own "use step", so a hiccup retries in isolation rather than replaying the fal call and the storage write above it. sendMusicNotification also swallows its own errors, matching sendSalesNotification's posture with Stripe webhooks.
generation is hoisted out of the try so the catch can still name the owning account; it stays undefined if the failure happened before the row was read, and the notification is skipped in that case.
Tests
14 assertions across three files, TDD. 81/81 green across lib/music and app/workflows; tsc and eslint clean.
Covers the link, the account, prompt and lyrics, long-lyric truncation, the real output length, the distinct failure shape, a missing email, test-account filtering, and the swallowed outage.
Two things worth knowing before merging
The test filter is narrower than it looks. isTestEmail matches two exact addresses (sweetmantech@gmail.com, sidney@recoupable.dev) rather than a pattern, so plus-addressed test accounts like sweetman+stamp@recoupable.com will notify. I wrote a test assuming otherwise and corrected it rather than widening a shared helper from inside this PR. Worth deciding separately whether that filter should be a pattern.
Volume. Eight generations exist today, so one message each is fine now and will not stay fine. The threshold is called out on chat#1999 rather than guessed at here.
Prompt and lyrics leave the system into a third-party chat. Lyrics are truncated to 400 characters, but this is a deliberate call, not a side effect — flagged on the issue.
🤖 Generated with Claude Code
https://claude.ai/code/session_017fSvwazBitPfsTQvVqpi8q
Summary by cubic
Notifies the admin Telegram chat when a music generation completes or fails, after storage and credit charge, so ops can listen quickly and react to failures. Previously there was no chat notification; now all accounts (including internal/test) notify to preserve observability.
- Runs as its own workflow step and never throws, so Telegram outages do not retrigger completed work.
- Success includes the song link, account email (or “unknown account”), prompt, truncated lyrics (400 chars), and the real output length; failure includes the link, account email, and error reason, and omits lyrics.
- Looks up the account email in the step; hoists the generation read so failures can still name the account, and skips notifying if the failure occurred before the row was read.
- Prompts and lyrics leave the system to Telegram; truncation limits message size but content disclosure is deliberate.
Summary by CodeRabbit
- New Features
- Added Telegram notifications for completed or failed music generations.
- Notifications include the song link, duration, prompt details, lyrics for successful generations, and failure reasons when applicable.
- Bug Fixes
- Improved reliability so notification delivery issues do not interrupt music generation or cause completed work to be retried.
- Failure notifications are sent when sufficient generation details are available.