Ever found yourself on-call, needing to parse a wall of alert text but your eyes are glazing over? I've been there. Sometimes hearing the information is better than reading it. That's why I built a simple Discord bot that uses ElevenLabs' text-to-speech API to read out messages in a voice channel. It's a neat way to get status updates or runbook steps read aloud while your hands are busy. The ElevenLabs voices are remarkably natural, which is a huge step up from the robotic TTS I've dealt with in other monitoring tools.
Here's the core of it using Python and discord.py. You'll need your ElevenLabs API key and a Voice ID from their site.
```python
import discord
from discord.ext import commands
import requests
import io
ELEVENLABS_API_KEY = "your_api_key_here"
VOICE_ID = "your_chosen_voice_id_here"
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
@bot.command(name='say')
async def tts(ctx, *, text):
if ctx.author.voice:
# Generate audio from ElevenLabs
url = f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}"
headers = {"xi-api-key": ELEVENLABS_API_KEY}
data = {"text": text, "voice_settings": {"stability": 0.5, "similarity_boost": 0.8}}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
# Connect to voice and play
vc = await ctx.author.voice.channel.connect()
audio_source = discord.FFmpegPCMAudio(source=io.BytesIO(response.content), pipe=True)
vc.play(audio_source)
while vc.is_playing():
await asyncio.sleep(1)
await vc.disconnect()
else:
await ctx.send(f"API error: {response.status_code}")
else:
await ctx.send("You need to be in a voice channel.")
bot.run("your_discord_bot_token")
```
A few observability tips from the trenches:
* **Rate Limits:** ElevenLabs has character-based limits. For reading alert summaries, it's fine, but don't try to stream a full postmortem. I learned that the hard way.
* **Error Handling:** The bot above is bare-bones. In production, you'd want proper timeouts, retries with exponential backoff for the API call, and logging for when the TTS fails.
* **Cost:** Keep an eye on your character usage if you're on a paid tier. It's easy to burn through it if the bot gets popular in a channel.
This is a fun weekend project that can actually be useful. I've used a version of it to have deployment status updates read out in our team's ops channel. It beats constantly tabbing over to check a log stream. Has anyone else integrated TTS into their ops workflow? Curious about other use cases.
-- nightowl
nightowl
A text-to-speech bot for alert fatigue is a clever workaround, I'll give you that. The latency on that ElevenLabs API call, though, is going to bite you during an actual incident when you need the information now, not after a 1.5-second round trip plus audio buffering.
You're also missing any instrumentation. Wrap that API request in a span or at least log its duration and status code. Otherwise, the first time the bot is silent during a P1, you'll have no idea if it's your code, their API, your network, or Discord's voice gateway that fell over. Vanity functionality needs production rigor too.
I'd also cache the audio byte stream for common alert phrases. No need to pay the latency and API cost to have it say "CPU utilization critical" for the hundredth time this week.
P99 or bust.
You're right about caching, but for a real on-call bot, you need to consider the cache invalidation boundary. If you're just caching on audio bytes, any change to the alert phrasing - even a single punctuation mark added by an engineer - becomes a new cache miss.
Instead, cache at the semantic level. Normalize the text before the API call: strip excess whitespace, remove non-critical punctuation, and convert to lowercase. This gives you a better cache hit ratio for things like "CPU: 95%" and "cpu 95%". You can even use a simple rolling hash of the normalized string as your cache key.
The more significant latency, in my experience, isn't the ElevenLabs API itself. It's the Discord voice connection setup and the audio buffer playback delay. If this bot isn't already in a voice channel, the join and handshake latency dwarfs the TTS generation time. Pre-warming the voice connection in a designated "alerts" channel can shave off 2-3 seconds when an actual incident happens.