A Val Town-powered newsletter system for curating and sending design engineering news twice a week.
- 📧 Email Subscription Management: Subscribe/unsubscribe endpoints
- 🔍 Automated News Search: Uses Tavily API to find latest design engineering content
- 🤖 AI-Powered Summaries: Hugging Face generates newsletter summaries
- ⏰ Scheduled Sending: Automatically sends twice weekly (Monday & Thursday at 9 AM UTC)
- 💾 SQLite Database: Tracks subscribers and newsletter history
- 📬 Resend Integration: Reliable email delivery
You need to set these environment variables in your Val Town project:
-
TAVILY_API_KEY - Get from Tavily
- Used for searching design engineering news
-
HUGGING_FACE_API_KEY - Get from Hugging Face
- Used for generating AI summaries
- Uses the
facebook/bart-large-cnnmodel
-
RESEND_API_KEY - Get from Resend
- Used for sending newsletter emails
-
ADMIN_API_KEY - Set this to a secure random string
- Used to authenticate manual newsletter triggers
- Generate with:
openssl rand -hex 32
-
FROM_EMAIL - Your verified sender email (e.g.,
newsletter@yourdomain.com)- Must be verified in Resend
- TAVILY_SEARCH_QUERY (optional)
- Default:
"design engineering news product design" - Customize the search query for finding articles
- Default:
Returns API information and available endpoints.
Subscribe to the newsletter.
Request:
{ "email": "user@example.com", "name": "John Doe" }
Response:
{ "message": "Successfully subscribed to Design Engineer Newsletter!", "email": "user@example.com" }
Unsubscribe from the newsletter.
Request:
{ "email": "user@example.com" }
Response:
{ "message": "Successfully unsubscribed", "email": "user@example.com" }
Manually trigger newsletter generation and sending (admin only).
Headers:
X-Admin-Key: your-admin-api-key
Response:
{ "message": "Newsletter sent successfully", "recipientCount": 42, "articlesCount": 10, "status": "success" }
id- Auto-increment primary keyemail- Unique email addressname- Subscriber name (optional)subscribed_at- Timestamp of subscriptionactive- Boolean flag (1 = subscribed, 0 = unsubscribed)
id- Auto-increment primary keysent_at- Timestamp of newsletter sendrecipient_count- Number of recipientsarticles_count- Number of articles includedstatus- Send status (success/failed)
The newsletter automatically sends twice a week:
- Monday at 9:00 AM UTC
- Thursday at 9:00 AM UTC
Schedule is configured using cron expression: 0 9 * * 1,4
main.ts- HTTP API handler with subscribe/unsubscribe endpointsnewsletter.ts- Core newsletter generation logic (Tavily, Hugging Face, Resend)scheduler.ts- Interval job for automated twice-weekly sending
curl -X POST https://your-val-url.val.run/subscribe \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com","name":"Test User"}'
curl -X POST https://your-val-url.val.run/admin/send \ -H "X-Admin-Key: your-admin-api-key"
You can customize the Tavily search query in two ways:
- Set the
TAVILY_SEARCH_QUERYenvironment variable - Pass a custom query when calling the admin endpoint (modify code as needed)
Example queries:
"design engineering product design UX UI""design systems engineering frontend""interaction design developer experience"
- Val Town uses Deno runtime - use
npm:imports for packages - Free tier has 15-minute minimum for interval jobs
- Pro tier allows 1-minute minimum intervals
- Resend batch API supports sending to multiple recipients efficiently
For issues or questions, refer to: