Launch a portfolio site with a working contact form

A fast personal site with projects, a contact form that really sends email, and your own domain with HTTPS.

24 घंटे beginner HTMLCSSFlaskResendRender

1 Friday evening: content before code

Write the words first. A portfolio that ships beats a beautiful empty one.

  • One sentence about what you do and for whom.
  • Three projects: problem → what you built → result (numbers if you have them).
  • One clear call to action: "Email me" or "Book a call".

Collect a screenshot per project (1600×900, compressed to under 300 KB with squoosh.app).

Done when: you have a text file with every word on the site.

2 Saturday morning: the page

One HTML file, one CSS file. Use a system font stack and a single accent colour.

<main class="wrap">
  <header>
    <h1>Hi, I'm Ada. I build web apps for small businesses.</h1>
    <a class="btn" href="#contact">Work with me</a>
  </header>
  <section id="work" class="grid">
    <article class="card">
      <img src="/static/img/shop.webp" alt="Online shop dashboard" width="800" height="450" loading="lazy">
      <h2>Bakery pre-orders</h2>
      <p>Cut phone orders by 60% with a simple pre-order form and daily email summary.</p>
    </article>
  </section>
</main>
:root { --bg: #0b0d11; --fg: #eceef3; --muted: #a7aebc; --accent: #2f6bff; }
body { margin: 0; font: 16px/1.6 system-ui, sans-serif; background: var(--bg); color: var(--fg); }
.wrap { width: min(1100px, 100% - 32px); margin-inline: auto; }
.grid { display: grid; gap: 24px; grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }

Done when: the page looks right at 390px and 1440px wide with no horizontal scroll.

3 Saturday afternoon: a contact form that sends email

Wrap the page in a tiny Flask app so the form can POST.

import os, resend
from flask import Flask, render_template, request, redirect, flash

app = Flask(__name__)
app.secret_key = os.environ["SECRET_KEY"]
resend.api_key = os.environ["RESEND_API_KEY"]

@app.post("/contact")
def contact():
    if request.form.get("website"):          # honeypot: bots fill hidden fields
        return redirect("/")
    resend.Emails.send({
        "from": "Site <hello@yourdomain.com>",
        "to": [os.environ["CONTACT_TO"]],
        "reply_to": request.form["email"],
        "subject": "New message from your site",
        "text": request.form["message"][:5000],
    })
    flash("Thanks! I will reply within a day.")
    return redirect("/#contact")

Add a rate limit (for example Flask-Limiter, 5 per hour per IP) and a CSRF token.

Done when: a message from the form lands in your inbox.

4 Sunday morning: deploy and connect the domain

  1. Push to GitHub and create a Render Web Service (gunicorn app:app).
  2. Set SECRET_KEY, RESEND_API_KEY and CONTACT_TO.
  3. In Render → Settings → Custom Domains add yourdomain.com and www.
  4. At your registrar add the records Render shows (an A/ALIAS for the apex, a CNAME for www).
  5. Verify the domain in Resend (SPF + DKIM records) so mail does not land in spam.

Done when: https://yourdomain.com loads with a padlock and the form still sends.

5 Sunday afternoon: check and share

  • Run Lighthouse in Chrome DevTools: aim for 90+ in every category.
  • Add <meta name="description"> and an Open Graph image.
  • Post the link where your clients are (LinkedIn, local groups), not only where developers are.

Done when: three people outside tech have opened the site.

प्रोजेक्ट पूरा हुआ? लिंक समीक्षा के लिए भेजें। समीक्षा के लिए भेजें (50 अंक)

इन ट्रैक के साथ और गहराई में जाएँ

और प्लेबुक