No copied tutorials: real infrastructure I designed, configured, and can explain in detail — including the security choices and trade-offs made along the way.
No servers to manage: just managed AWS services working together. Below, every implementation choice with its reasoning — in the format I actually use to document architecture decisions.
[ADR Architecture Decision Record]
Decision: the S3 bucket is never public. Public access is explicitly blocked on all settings (ACLs, bucket policy, public bucket). The only way to read the files is through CloudFront, authorized via Origin Access Control (OAC).
Why: a public bucket exposes files to direct reads from anyone who knows the URL, with no caching, no automatic HTTPS, no protection from abuse (hotlinking, mass scraping that drives up S3 request costs). Routing everything through CloudFront centralizes all of this: free HTTPS via a managed certificate, distribution from global edge locations, and a single entry point to secure instead of two.
Decision: no operation on this project uses a broadly-permissioned IAM user. There's a dedicated role with a policy listing only the actions needed on explicitly named resources — no generic wildcards. My base user assumes this role only when needed, getting temporary credentials.
Why: this is the least-privilege principle applied concretely, not just in theory: if a credential for this project were ever compromised, the blast radius is limited to that role's permissions — no access to other projects, no access to EC2 or other unused services here. It's also the pattern used in real multi-project company environments.
Decision: all resources (S3 bucket, Lambda function) are created in Frankfurt
(eu-central-1). CloudFront remains a global service, not tied to a region.
Why: lower latency working from Europe, and consistency with where I'm based. The one
known technical exception: if a custom domain is added later, the TLS certificate for CloudFront
must be requested in us-east-1 — a hard-coded AWS constraint, not negotiable. Not
relevant right now since the site uses CloudFront's default domain.
Decision: the site is reachable via the default URL assigned by CloudFront
(*.cloudfront.net), with no Route 53 and no registered domain.
Why: for the goal of this project — validating the S3 → CloudFront → Lambda/SES pipeline — a custom domain adds no relevant technical learning, only a recurring cost (~$12-15/year).
Decision: the form's email delivery is handled by a Lambda function calling Amazon SES directly, instead of an external service (e.g. Formspree, EmailJS).
Why: the point of this project is demonstrating command of the AWS serverless stack, not just getting a form to work.
Decision: every resource in this project was created with direct AWS CLI commands, not CloudFormation or Terraform.
Why: my study plan treats Infrastructure as Code as a separate, later phase. Building by hand first forces a real understanding of what each resource actually does (bucket policies, IAM trust policies, OAC configuration) instead of hiding it behind a template. This same project will later be rebuilt in Terraform as a consolidation exercise, once that phase of the path is covered.
Fill in the form and your message comes straight to me. If you'd like, you can also receive an email with a technical breakdown of the technologies used in this project.