I built this website in an afternoon back in June 2016. It was a basic HTML/CSS template hosted on AWS S3 with Route 53 for DNS. The contact form didn't work because S3 only serves static content; I knew that prior to building but wanted to get something live in MVP fashion.

Nearly ten years later the site is still going. It's been rebuilt twice, moved hosting platforms, swapped CI/CD tools, and picked up a contact form that actually works. This post covers how it evolved and what I learned along the way.

The Original Stack (2016)

The first version was simple: static HTML and CSS hosted in an S3 bucket with CloudFront providing SSL. I used Bootstrap for layout, jQuery for interactivity, and Font Awesome for icons. The whole thing was uploaded manually at first.

The contact form was the first real challenge because S3 doesn't support server-side scripting, so I needed a different approach. I ended up using the AWS API Gateway, with a Lambda function written in Python to process form submissions, and AWS SES to send the emails. Google reCAPTCHA v2 handled bot prevention.

Back in 2016 that was a lot of head-scratching!

It took about a week of tinkering to get the form working end to end. The moving parts were: HTML form, jQuery/AJAX to post the data as JSON, API Gateway with a POST method and mapping template, Lambda to validate reCAPTCHA and call SES, and a verified email address in SES to receive the messages.

Here's a simplified view of how the pieces connected:

Original AWS contact form architecture (2016) HTML form User input jQuery AJAX JSON POST API Gateway POST /myform Lambda (Python) Process + validate Google reCAPTCHA Verify token AWS SES Send email Email delivered

Once the form was sorted I turned my attention to deployment because I was uploading files manually at first; then in August 2016 I set up Jenkins on a local machine to automate it. Jenkins would sync the local Git branch to the S3 bucket and I had separate branches for test and production.

The Quiet Years (2017-2023)

Life and work got in the way and there were long stretches where nothing happened. The 2017 updates were mostly maintenance and minor features: adding server-side reCAPTCHA verification and moving to the new AWS London region (just because). In 2019 I changed the homepage picture. In 2021 I had to update the Lambda function to Python 3.9 because AWS deprecated 2.7.

I mention this because I think it's worth being honest about how personal projects actually work. They don't get consistent attention. You come back to them when you have the energy and motivation; sometimes that's months or years apart.

The 2024 Rebuild

In late 2024 I picked the site back up properly. I had a list of things to sort:

  • jQuery had a known CVE; updated to version 3.x
  • Bootstrap was ancient; updated to version 4
  • Jenkins felt outdated and so I switched to GitHub Actions
  • The projects page was a basic placeholder; rebuilt it with sidebar filtering and markdown rendering
  • The history page needed a proper timeline format with search

The GitHub Actions migration was significant. I created an action that connects to AWS via OIDC (no stored credentials), uploads changed files to the S3 bucket, and invalidates those files in CloudFront. It triggers on commit to main. I published it on the GitHub Marketplace in case anyone else finds it useful: github-action-s3-and-cloudfront.

The projects page was the bigger piece of work. I wanted a proper documentation-style layout where I could write articles in markdown and have them rendered on the page. The result has a sidebar with category and tag filtering, and articles that load from markdown files using Marked.js. It made it much simpler to create and maintain content.

The 2026 Redesign: Moving to Vercel

In 2026 I made the decision to redesign the entire site and Paradigm Shift template from HTML5 UP was used as a general basis; I came across it during the Python course in 2025. This wasn't just a cosmetic change, it was also a shift in how the site was hosted and deployed.

The key changes:

  • Moved from S3/CloudFront to Vercel
  • Based the main site on the Paradigm Shift template with a mint (#49fcd4) accent colour
  • Switched fonts to Raleway and Source Sans Pro
  • Added a dark mode toggle using [data-theme="dark"]
  • Replaced the AWS contact form stack with a single Vercel serverless function using Mailgun
Architecture comparison: AWS stack vs Vercel stack Before (AWS) Hosting S3 bucket CloudFront CDN Route 53 DNS Contact form API Gateway Lambda (Python) AWS SES CI/CD: GitHub Actions → S3 sync After (Vercel) Hosting Vercel CDN + DNS + deploy Contact form /api/contact.js Serverless function Mailgun EU Send email CI/CD: git push → auto-deploy 6 services → 2 Simpler to maintain

The contact form went from five AWS services to one serverless function. The api/contact.js function calls the Mailgun EU endpoint (api.eu.mailgun.net) to send the email.

The DNS side had its own lessons. SPF and DKIM records are needed for Mailgun to verify outbound email; I already had an SPF record for another service and so needed to merge them into a single TXT record rather than creating a second one.

Deployment became trivial because Vercel auto-deploys from the GitHub repo on every push. No more managing S3 sync scripts or CloudFront invalidations. Vercel Analytics and Speed Insights were added with a couple of <script defer> tags.

What Stayed the Same

Through all these changes the core philosophy hasn't shifted. The site exists to learn new skills and technologies. The main page is intentionally built without frameworks where possible; the current version is still plain HTML, CSS and JavaScript with no React or build step. Wider pages use different frameworks and technologies as I continue to learn.

Every technical decision is documented somewhere: in the history page, in project write-ups, or in this blog. That documentation habit started in 2016 and it's one of the most valuable thing I've maintained.

What I'd Tell Someone Starting Out

Don't overthink the first version. My initial site was an HTML template in an S3 bucket with a broken contact form. It was live in an afternoon. Everything else came incrementally.

Pick real problems to solve. The contact form taught me about API Gateway, Lambda, SES, CORS, reCAPTCHA integration, and eventually serverless functions on a different platform. I wouldn't have learned any of that from a tutorial alone.

Document as you go. The history page on this site is one of my favourite features because it's a genuine record of what happened, including the gaps where nothing happened at all.

And finally: it's fine to leave things for months or years. Personal projects aren't sprints. They're there when you're ready to come back to them.