Live site: Mayank's Portfolio
Backend API: Backend's Health Check
A professional full-stack portfolio: a static site (HTML/CSS/JS) front-end with a Spring Boot REST API backend. Features include a contact form (emails via SendGrid), resume download (latest PDF), and a responsive, animated UI. Deployed on Vercel (frontend) and Railway (backend).
| Layer | Technology |
|---|---|
| Frontend | HTML5 ยท CSS3 ยท Vanilla JS |
| Backend | Java 17 ยท Spring Boot 3.2 ยท Spring Web |
| SendGrid API (Java SDK) | |
| Validation | Jakarta Bean Validation |
| Rate Limiting | Bucket4j (3 req/IP/hour) |
| Deployment | Vercel (FE) ยท Render (BE) |
| Container | Docker (multi-stage, non-root) |
- Contact Form: Visitor submits name/email/message. Backend uses SendGrid to email the owner and send a confirmation. Input validated (Jakarta Validation) and rate-limited (3 requests/IP/hour).
- Resume Download: Latest resume (
resume-v3.pdf) served via/api/resume/download. The oldresume.pdfis kept as a backup. - Responsive UI: Mobile-friendly portfolio with scroll fade-in animations and a typing text effect.
- Secure: CORS restricted to the frontend domain; no secrets in code (all configs via environment variables); uses HTTPS deployment.
- Offline Fallback: If the email API fails, the page displays a
mailto:link for manual contact as a fallback. - Observability: Example logging of resume downloads and contact submissions (in-memory counters, with suggestion to persist in a DB for production).
Mermaid sequence diagram for request flow:
sequenceDiagram
participant U as User
participant F as Frontend
participant B as Backend (Spring Boot API)
participant S as SendGrid
participant E as Email Inbox
U->>F: Fill contact form
F->>B: POST /api/contact (name, email, subject, message)
B->>S: SendGrid.sendEmail()
S->>E: Deliver email (owner & user)
E-->>U: User receives confirmation email
U->>F: Click resume download
F->>B: GET /api/resume/download
B-->>F: Return Resume PDF
F->>U: Prompt file download
mayankgupta-portfolio/
โโโ portfolio/ โ Frontend (static site)
โ โโโ index.html โ Main HTML, CSS, JS
โ โโโ photo.jpg โ Profile picture
โ โโโ og-image.png โ Open Graph preview image
โ
โโโ portfolio-backend/ โ Spring Boot REST API
โโโ src/
โ โโโ main/java/dev/mayank/portfolio/
โ โโโ controller/
โ โ โโโ ContactController.java โ POST /api/contact
โ โ โโโ ResumeController.java โ GET /api/resume/download
โ โ โโโ PortfolioController.java โ GET /api/health, /api/info
โ โโโ service/
โ โ โโโ ContactService.java โ Email sending (SendGrid)
โ โโโ model/ โ DTOs (ContactRequest, ApiResponse, etc.)
โ โโโ config/ โ CORS config, global exceptions
โโโ src/main/resources/static/
โ โโโ resume-v3.pdf โ Latest resume PDF
โ โโโ resume.pdf โ Old resume (backup)
โโโ Dockerfile
โโโ pom.xml
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Health check (returns status) |
| GET | /api/info |
Portfolio info (owner name/tagline) |
| POST | /api/contact |
Handle contact form submission (JSON) |
| GET | /api/resume/download |
Download latest resume PDF |
cd portfolio
# Serve statically (no build needed)
python3 -m http.server 3000Open http://localhost:3000 in your browser.
cd portfolio-backend
# Set environment variables (example for Unix/Mac)
export SENDGRID_API_KEY=SG.xxxxxxxx
export CONTACT_EMAIL=ce.mayank8@gmail.com
export FROM_EMAIL=ce.mayank8@gmail.com
export FRONTEND_URL=http://localhost:3000
export PORT=8080
mvn spring-boot:runThe API will be available at http://localhost:8080.
# Health check
curl http://localhost:8080/api/health
# Submit contact form (replace with your own data)
curl -X POST http://localhost:8080/api/contact \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com","subject":"Hello","message":"This is a test."}'
# Download resume PDF
curl -L http://localhost:8080/api/resume/download --output Mayank_Gupta_Resume.pdfA successful contact form submission returns {"success":true} and sends emails to both you and the visitor. The resume download command saves Mayank_Gupta_Resume.pdf locally.
- Backend (Render):
- Go to render.com and sign in with GitHub
- Click "New +" โ "Web Service"
- Connect your
mayankgupta-portfolioGitHub repository - Set the following:
- Name: portfolio-backend
- Environment: Docker
- Root Directory:
portfolio-backend - Auto-Deploy: Yes (deploy on push)
- Add environment variables in the Render dashboard:
SENDGRID_API_KEY=YOUR_SENDGRID_KEY CONTACT_EMAIL=ce.mayank8@gmail.com FROM_EMAIL=ce.mayank8@gmail.com FRONTEND_URL=https://your-portfolio.vercel.app PORT=8080 - Frontend (Vercel): Connect the
portfoliofolder on Vercel (Framework: Other / Static). After deploying, update theAPI_BASEURL inindex.htmlto your Render backend URL(e.g. https://portfolio-backend.onrender.com).
| Variable | Purpose |
|---|---|
SENDGRID_API_KEY |
API key for sending emails |
CONTACT_EMAIL |
Email to receive contact form |
FROM_EMAIL |
Verified sender email address |
FRONTEND_URL |
Frontend domain (for CORS) |
PORT |
Server port (default 8080) |
- CORS Errors: Ensure your backend allows your frontend origin. For example, use
@CrossOrigin(origins="https://your-portfolio.vercel.app")on controllers or configure global CORS to include your Vercel domain orhttp://localhost:3000for local dev. - SendGrid 401 Unauthorized: Make sure SENDGRID_API_KEY is correctly set in your environment (Render/local).
- SendGrid 403 Forbidden: The
FROM_EMAILmust be a verified sender identity in SendGrid (via Single Sender Verification or Domain Authentication). Use a verified email (e.g. your Gmail) or set up domain authentication to fix this. - Email Not Received: Check SendGrid Email Activity for delivery, bounces, or drops. A "Dropped" status often indicates unverified sender or spam filtering.
- Broken Email Link (404 /cdn-cgi): Remove any Cloudflare
/cdn-cgi/email-protectionscript from the HTML. Use a directmailto:link instead (the JS fallback sets this up). - API Fallback: If the contact form submission fails (network issue), the page will display a
mailto:link so users can email you directly.
- Resume Downloads: You can log or count downloads in
ResumeController. Example:@GetMapping("/api/resume/download") public ResponseEntity<Resource> downloadResume() { int count = downloadCount.incrementAndGet(); System.out.println("Resume downloads: " + count); Resource res = new ClassPathResource("static/resume-v3.pdf"); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=Mayank_Gupta_Resume.pdf") .contentType(MediaType.APPLICATION_PDF) .body(res); }
- Contact Submissions: In
ContactControllerorContactService, increment a counter on each form submission:int leads = contactCount.incrementAndGet(); System.out.println("Contacts received: " + leads);
- Stats Endpoint (Optional): For advanced tracking, add
/api/stats:In production, consider persisting these stats to a database (e.g. PostgreSQL) with timestamps.@RestController @RequestMapping("/api/stats") public class StatsController { private AtomicInteger resumeDownloads = new AtomicInteger(); private AtomicInteger contacts = new AtomicInteger(); @GetMapping public Map<String,Integer> getStats() { return Map.of("resumeDownloads", resumeDownloads.get(), "contacts", contacts.get()); } }
- Email Authentication: Configure SendGrid domain authentication and SPF/DKIM records for a custom sender domain, improving deliverability.
- Persistent Leads: Store contact messages and stats in a database for audit/analytics (e.g. PostgreSQL, MongoDB).
- Analytics Dashboard: Build a simple admin dashboard or integrate third-party analytics to visualize contacts and resume download stats.
- CI/CD & Testing: Set up GitHub Actions for automated builds/tests and deploy pipelines.
- SEO & Social: Add a sitemap, robots.txt, and dynamic Open Graph images for better SEO and link previews.
- UI Enhancements: Dark mode toggle, a blog or project filter section, and improved accessibility features.
Mayank Gupta โ Senior Backend Engineer
๐ง ce.mayank8@gmail.com
๐ LinkedIn ยท GitHub