I was reading about the Shipping Forecast and it reminded me of how fascinated I was by the weather information numbers that used to exist when I was a kid. I decided to make one myself, with a twist. -ka
Phone-based weather report service narrated in the style of VH1's "Behind the Music." Dial in, enter your zip code, and hear a dramatic forecast delivered like the greatest story ever told.
- You dial the Twilio phone number
- You enter your 5-digit zip code on the keypad
- The app looks up your location, fetches the forecast from the National Weather Service, generates a dramatic narrator script, and converts it to speech
- You hear your weather report
- Elixir 1.15+
- Erlang/OTP 26+
mix setup
mix phx.serverThe server starts at localhost:4000. You'll need the environment variables configured for full functionality.
Set these before running in development or production:
| Variable | Description |
|---|---|
OPENROUTER_API_KEY |
Script generation via OpenRouter |
TWILIO_ACCOUNT_SID |
Twilio account SID for phone integration |
TWILIO_AUTH_TOKEN |
Twilio auth token for phone integration |
Configure one of the following voice providers:
ElevenLabs:
| Variable | Description |
|---|---|
ELEVENLABS_API_KEY |
API key |
ELEVENLABS_VOICE_ID |
Voice ID for the narrator |
Fish Audio:
| Variable | Description |
|---|---|
FISH_AUDIO_API_KEY |
API key |
FISH_AUDIO_MODEL_ID |
Model ID |
FISH_AUDIO_REFERENCE_ID |
Reference ID for voice cloning |
- Start an ngrok tunnel to your local server:
ngrok http 4000
- Copy the
httpsforwarding URL from the ngrok output - In your Twilio phone number settings, set the voice webhook to
https://YOUR_NGROK_URL/webhooks/twilio/voice - Call your Twilio number to test
Caller → Twilio → POST /webhooks/twilio/voice
↓
TwiML: welcome + <Gather> zip code
↓
POST /webhooks/twilio/gather
↓
┌───────────────────────┐
│ Processing Pipeline │
│ │
│ Zip → Geocoder (ETS) │
│ Lat/Lon → NWS API │
│ Forecast → OpenRouter │
│ Script → Voice (TTS) │
└───────────┬───────────┘
↓
TwiML: <Play> MP3 + <Hangup>
BehindTheWeather.Geocoder— Zip code to lat/lon lookup via ETS table loaded from bundled CSVBehindTheWeather.Weather— NWS API client and forecast parsingBehindTheWeather.Script— OpenRouter client; generates the narrator scriptBehindTheWeather.Voice— Voice synthesis (ElevenLabs or Fish Audio); converts script to MP3BehindTheWeather.AudioStore— Temporary MP3 storage with automatic cleanup
The app handles failures automatically so callers always get a response:
- Invalid zip code — Re-prompts the caller
- NWS API failure — "Temporarily unavailable" message + hangup
- Script or voice generation failure — Falls back to Twilio
<Say>with a plain weather summary - 15-second timeout — If the pipeline takes too long, serves the fallback