Uploading and publishing (content team)
This guide assumes engineering has already created the Cloudflare R2 bucket and Postgres is connected to the app. Day-to-day publishing can stay SQL-based until the admin UI ships.
1. Upload the MP3 to R2
- Log into Cloudflare → R2 → bucket
askelephant-audio-library(or your configured bucket). - Upload the file. Prefer descriptive object keys, e.g.
lessons/billing-overview.mp3. - Ensure the object is public (or served via a public bucket domain).
- Copy the full HTTPS URL. Test it in a browser tab; audio should play.
2. CORS
The bucket must allow GET (and HEAD) from:
https://learn.askelephant.ai- Your Knowledge Base origin (e.g.
https://support.askelephant.ai) http://localhost:3000for local testing
See the main README for a sample CORS JSON block.
3. Insert the lesson in Postgres
Ask engineering to run SQL (or use a SQL client against the prod DB), for example:
INSERT INTO audio_resources ( slug, title, description, audio_url, status, published_at ) VALUES ( 'billing-overview', 'Billing overview', 'Short tour of the billing screen.', 'https://YOUR-R2-PUBLIC-URL/lessons/billing-overview.mp3', 'published', NOW() );
Use a unique slug (URL segment). Set status to published when ready to go live; use draft while iterating.
4. Tags (optional)
INSERT INTO tags (name, slug) VALUES ('Billing', 'billing') ON CONFLICT (slug) DO NOTHING; INSERT INTO audio_resource_tags (audio_resource_id, tag_id) SELECT ar.id, t.id FROM audio_resources ar CROSS JOIN tags t WHERE ar.slug = 'billing-overview' AND t.slug = 'billing' ON CONFLICT DO NOTHING;
5. Verify
Open https://learn.askelephant.ai/library and confirm the lesson appears. Open the lesson page and confirm playback and transcript (if any).
Cache headers (R2)
For best performance, set long Cache-Control on static MP3 objects (e.g. public, max-age=31536000, immutable) when filenames are versioned or busted on replace. Coordinate with engineering before changing caching on mutable URLs.