RETURN_TO_REGISTRY

Serverless Portfolio & CMS Platform

AzureGithubCloudflareMongoDbResendCMS.NetEventGrid
Serverless Portfolio & CMS Platform

Short Summary

I designed and built otdev.io as more than a static portfolio. It is a full-stack, serverless portfolio and CMS platform built with Next.js, Azure Static Web Apps, Azure Functions, MongoDB Atlas, Cloudflare R2, Azure Event Grid, Resend, and Google Analytics.

The platform includes a public portfolio, project pages, blog content, an authenticated CMS dashboard, media uploads, contact form processing, transactional email automation, and background maintenance workflows.

Core stack and integrations powering the otdev.io portfolio platform.

Why I Built It

I wanted a portfolio that I could fully control, extend, and maintain like a real software product. Instead of using a simple website builder or a static resume page, I built a custom platform where I can manage my professional profile, projects, blog posts, contact messages, email templates, and uploaded media from an admin dashboard.

The goal was to create something practical, scalable, and aligned with how I think: systems should be organized, visible, maintainable, and easy to improve over time.

This project also gave me a clean way to combine my background in industrial systems with software development. The result is a platform that connects frontend design, backend APIs, authentication, database storage, object storage, event-driven workflows, and external services into one working system.

Architecture Overview

The frontend is built with Next.js and hosted through Azure Static Web Apps, with Cloudflare handling DNS, routing, and edge delivery. The frontend includes both the public portfolio interface and the CMS dashboard.

The backend is built with Azure Functions using .NET, exposing REST endpoints for public content and protected admin operations. Public endpoints serve profile data, projects, blog posts, settings, templates, and health checks. Protected endpoints support CMS operations such as creating, updating, and deleting projects or blog posts, managing profile content, updating settings, handling contact messages, and uploading media.

Authentication for admin operations is handled through Azure Entra ID, allowing the CMS side of the application to remain protected while keeping the public portfolio accessible.

Data is stored in MongoDB Atlas, including profile content, projects, blog posts, contact messages, settings, and email template metadata. Uploaded media is stored in Cloudflare R2, which keeps the media layer separate from the application database.

External services include Resend for transactional email delivery and Google Analytics for traffic insights.

Contact Form and Email Automation

One of the most important workflows in the platform is the contact form. When a visitor sends a message, the platform does not simply send an email directly from the request. Instead, the system stores the message first and then processes the email workflow asynchronously.

The frontend sends the form submission to the SubmitContactForm Azure Function. The API validates the input, stores the contact message in MongoDB Atlas, and publishes a contact event to Azure Event Grid.

Azure Event Grid then triggers the EmailDispatcher function. That function calls Resend to send two transactional emails: a custom auto-reply to the client and a notification email to the site owner with a message summary.

This design keeps the contact submission reliable and avoids coupling the user-facing request directly to the email delivery process.

Contact submissions are stored first, then processed asynchronously through Azure Event Grid and Resend.

Media Uploads and Maintenance

The CMS includes media upload support for images and other assets used in project and blog content. Uploaded files are stored in Cloudflare R2, while the related references are managed through the backend and MongoDB.

To keep the storage layer clean, the platform includes a scheduled maintenance function called MediaGarbageCollector. This timer-triggered Azure Function runs weekly and checks for uploaded media that is no longer referenced by active content.

The function compares stored media references with objects in Cloudflare R2 and removes orphaned files. This prevents unused images from accumulating over time and keeps the system easier to maintain.

Oscar Calix

Oscar Calix

Control System Engineer

Cloud computing for OT, industrial data pipelines, SCADA supervision, and full-stack tools for automation workflows.

Serverless Portfolio & CMS Platform | Projects | Oscar Calix