This is where my personal blog and portfolio lives.
The blog is powered by Hugo. Enjoy!
What things you need to install the software and how to install them
brew install hugoNode.js LTS (v20+) is also required for CSS compilation.
Clone the repo and install dependencies:
git clone git@github.com:batjaa/blog.git
cd blog
npm installExplain what these tests test and why
Give an example
To compile CSS and start the Hugo development server:
npm run devThis will:
- Compile the Less files to CSS
- Start Hugo server with cache clearing and static file syncing
- Watch for changes (Hugo hot-reload)
To compile CSS separately:
npm run css:buildTo watch for CSS changes during development:
npm run css:watchTo compile CSS and build the site for production:
npm run buildCreate a .env file in the root directory (use .env.example as a template):
cp .env.example .envConfigure the following optional features:
- Instagram Feed: Now uses manually curated posts (no API token needed)
- To update the displayed posts, edit
themes/mongkok/layouts/partials/instagramfeed.html - Simply update the post IDs in the
$postsarray
- To update the displayed posts, edit
- GOOGLE_MAPS_API_TOKEN: For displaying Google Maps
- Get your API key from Google Cloud Console
- For production use, create a Map ID in Google Cloud Console and update the
mapIdinthemes/mongkok/assets/js/post.js
Monthly newsletter system that generates both email-ready HTML and a web archive.
npm run newsletter:install# Create newsletter for current month
npm run newsletter:new
# Or specify a month
npm run newsletter:new 2026-02This creates newsletter/issues/YYYY-MM.md with a template.
Edit the markdown file in newsletter/issues/. The file has two parts:
1. YAML Frontmatter - Structured data for special sections:
---
title: "February 2026"
date: 2026-02-01
featured_image: https://your-cdn.com/hero.jpg
trading:
pnl: "+$1,234"
sentiment: positive # positive | negative | neutral
chart: https://your-cdn.com/chart.png
movies:
- url: https://www.imdb.com/title/tt1234567/
comment: "Great movie!"
books:
- title: "Book Title"
author: "Author Name"
status: reading # reading | finished | abandoned
---2. Markdown Body - Prose sections using H2 headers:
## Family
Updates about the family...
## Professional
Work updates...
## Health
Fitness progress...
## Travel
Adventures...# Build the newsletter (generates HTML for email + web archive)
npm run newsletter:build
# Preview in browser (hot-reload)
npm run newsletter:preview
# Send test email to yourself
npm run newsletter:send:test# Build everything for production
npm run newsletter:build && npm run buildThe newsletter will be available at /newsletter/YYYY-MM/ on the site.
Upload images to S3 (served via CloudFront):
aws s3 cp photo.jpg s3://batjaa-blog-email-media/2026/02/photo.jpgThen reference in your newsletter as:
https://d2f2jpla7ooipu.cloudfront.net/2026/02/photo.jpg
For sending emails, set these in your .env (local) or GitHub Secrets (CI):
# Required
POSTMARK_API_KEY=your-api-key
TEST_EMAIL_ADDRESS=your@email.com
TURSO_DATABASE_URL=libsql://your-db.turso.io
TURSO_AUTH_TOKEN=your-token
NEWSLETTER_TOKEN_SECRET=generate-a-long-random-secret
# Optional
NEWSLETTER_FROM_EMAIL=newsletter@batjaa.comFor Postmark webhooks, configure:
- URL:
https://batjaa.com/api/postmark/webhook - Header
X-Postmark-Server-Token: value fromPOSTMARK_WEBHOOK_TOKEN(if set)
The newsletter workflow (.github/workflows/newsletter.yml) handles both automatic builds and manual email sending.
When you push changes to newsletter/issues/** or newsletter/templates/** on the master or main branch:
- Build - Renders markdown issues into HTML
- Netlify auto-deploys - Triggered by the push to master
Go to Actions → Newsletter → Run workflow to manually trigger:
| Action | Description |
|---|---|
build-only |
Build newsletter HTML without sending |
send-test |
Build and send to your test email address |
send-broadcast |
Build and send to all subscribers |
| Secret | Description |
|---|---|
POSTMARK_API_KEY |
Postmark API key for sending emails |
TEST_EMAIL_ADDRESS |
Email address for test sends |
TURSO_DATABASE_URL |
Turso database URL for subscriber list |
TURSO_AUTH_TOKEN |
Turso auth token |
NEWSLETTER_TOKEN_SECRET |
HMAC secret used for unsubscribe links |
NEWSLETTER_FROM_EMAIL |
(Optional) Sender email address |
POSTMARK_WEBHOOK_TOKEN |
(Optional) Verifies Postmark webhook requests |
OMDB_API_KEY |
(Optional) OMDB API key for movie metadata enrichment |
./deploy.sh [commit message]