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.
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
- Push to GitHub and create a Render Web Service (
gunicorn app:app). - Set
SECRET_KEY,RESEND_API_KEYandCONTACT_TO. - In Render → Settings → Custom Domains add
yourdomain.comandwww. - At your registrar add the records Render shows (an
A/ALIASfor the apex, aCNAMEforwww). - 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.
इन ट्रैक के साथ और गहराई में जाएँ
और प्लेबुक
- schedule_sendAutomate a daily report with Python, cron and emailA script that pulls data every morning, builds a short summary with a chart, and emails it to you, running unattended on a small Linux server.
- account_balance_walletBuild an offline budget tracker app (PWA)An installable app that records income and expenses, works offline, shows a monthly chart and exports to CSV, with no server and no account.
- event_availableBuild an online booking system for a small businessCustomers pick a free time slot, book it and get an email confirmation; the owner sees and cancels bookings in a simple admin page.