You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
✅ Phase 11.6: Email Service Integration (Production-Ready) #42
Send email notification when the contact form is submitted
Use Spring Boot Mail (SMTP)
Secure credentials using .env (Docker + VPS)
Production-ready setup (no hardcoding secrets)
This is exactly the right stage to introduce email as a side-effect service in our backend. Since our stack is already production-oriented (Docker, CI/CD, Redis, PostgreSQL), we’ll implement this cleanly with separation of concerns, async handling, and secure config.
We’ll implement this in 7 structured steps:
1️⃣ Choose SMTP Provider (Critical Decision)
Use a reliable SMTP provider—do NOT use raw Gmail in production unless it's temporary.
Recommended:
SendGrid (best balance)
Mailgun
Amazon SES (best for scale)
👉 For now, I’ll assume Gmail SMTP (quick start), but the structure will be provider-agnostic.
=========================== 🧪 After Fix — Verify Again
Your debug output should now show:
SMTP timeout = 5000
🧪 Expected Behavior After Fix
BEFORE: ~16 sec delay ❌ AFTER: ~2–5 sec OR fail at 5 sec ✅
✅ Final Verdict
Check Status
Async working ✅ Perfect
Docker integration ✅ Correct
Thread pool ✅ Working
Non-blocking API ✅ Confirmed
Production readiness ⚠️ 80% (needs retry system)
🚀 IMPORTANT — Rebuild Required
For [Local Machine - Docker]
docker-compose down
docker-compose --env-file .env.prod up --build
🔥 Where We Stand Now
You’ve just implemented:
Async processing + Thread pool + SMTP optimisation
===================
REMOVE REDIS DEPENDENCY
🔥 When Should We Use Redis?
Only when you need:
High throughput queues
Distributed systems
Event streaming
👉 Not required for your current scale
⚠️ What NOT to Touch
✅ Keep PostgreSQL
✅ Keep your Spring Boot app
✅ Keep Docker networking
✅ Keep Monitoring [Prometheus + Grafana]
I have not mentioned below in my .env file on the VPS.
My code uses this line-> MAIL_HOST=smtp.zoho.in
And I mentioned in VPS .env->below line MAIL_HOST=smtp.zoho.com
=========================
After last, I forgot to put the below line in my vps .env
APP_ADMIN_EMAIL=vimal.patel@vimaltech.dev
===============================
After the update, which command runs to update the application?
🔴 Actual Failure (From My Logs)
This is the only thing that matters:
No qualifying bean of type 'JavaMailSender'
And specifically:
Failed to load ApplicationContext in test
ContactApiApplicationTests.contextLoads
🔥 What Is Actually Happening
From logs:
activeProfiles = ["test"]
👉 Your GitHub Actions is running:
@SpringBootTest
Which:
Loads full Spring context
Uses test profile
DOES NOT have mail config
💥 Why It Fails
Our dependency chain:
ContactController
→ ContactService
→ SmtpEmailService
→ JavaMailSender ❌ (missing in test env)
I have not mentioned below in my .env file on the VPS. My code uses this line-> MAIL_HOST=smtp.zoho.in And I mentioned in VPS .env->below line MAIL_HOST=smtp.zoho.com
========================= After last, I forgot to put the below line in my vps .env APP_ADMIN_EMAIL=vimal.patel@vimaltech.dev
=============================== After the update, which command runs to update the application?
🔴 Actual Failure (From My Logs)
This is the only thing that matters:
No qualifying bean of type 'JavaMailSender'
And specifically:
Failed to load ApplicationContext in test
ContactApiApplicationTests.contextLoads
🔥 What Is Actually Happening
From logs:
activeProfiles = ["test"]
👉 Your GitHub Actions is running:
@SpringBootTest
Which:
Loads full Spring context
Uses test profile
DOES NOT have mail config
💥 Why It Fails
Our dependency chain:
ContactController
→ ContactService
→ SmtpEmailService
→ JavaMailSender ❌ (missing in test env)
Activity
vimaltech-dev commentedon Apr 8, 2026
Assign me:
Would be part of the last service.
vimaltech-dev commentedon Apr 9, 2026
✅ Instead of using Gmail SMTP
✅ Updated Decision (Better Architecture)
👉 Used Zoho SMTP directly
Why this is the correct move:
🔧 Zoho SMTP Configuration
Use this instead:
🚀 STEP 1️⃣ — Choose SMTP Provider (Decision First)
1️⃣ Enable App Password (Recommended)
2️⃣ SMTP Settings (Confirmed)
We are now doing:
Frontend → API → DB save → Email (Zoho SMTP via domain email).This is exactly how real production systems work.
🚀 STEP 2️⃣ — Add Dependency (Zero Risk)
This step is completely safe:
📌 What this does (internally)
👉 Nothing executes yet — it’s just wiring capability.
🚀 STEP 3️⃣ — Environment Variables (SECURE + VPS-SAFE)
🧩 Goal
Inject SMTP credentials via:
.env → Docker → Spring Boot✅ 3.1 — Update .env on VPS
(Our Current Architecture)
🔹 Infra Stack (vimaltech-contact-api)
/opt/contact-api/.env🔹 Deployment Stack (deployments/contact-api)
👉 This is PERFECT — centralized env management.
✅ 3.2 — Verify Docker Compose Uses .env
✅ Key Conclusion
👉 We DO NOT need to change Docker Compose at all
Our system is already correctly wired to support email env variables.
Add mail config there: at /opt/ location
===================
⚠️ Important Insight
Because BOTH:
already use:
env_file: /opt/contact-api/.env👉 Email config will automatically be available to BOTH containers.
✅ 3.3 — Restart Containers (SAFE WAY)
When you ran:
Docker Compose tried to resolve variables in docker-compose.yml, like:
SPRING_DATASOURCE_URL: jdbc:postgresql://vimaltech-postgres:5432/${POSTGRES_DB}❗ Problem
Compose does NOT read /opt/contact-api/.env automatically for variable substitution.
👉 It only auto-loads:
./.env (same directory as docker-compose.yml)💥 Why You Saw a Warning
POSTGRES_DB variable is not set.👉 Because Compose couldn’t find it in:
✅ FIX (Clean & Production-Safe)
✔️ Why this works
Now Compose can:
✅ Verification (MANDATORY)
Run:
docker exec -it vimaltech-contact-api-blue env | grep MAIL
and:
docker exec -it vimaltech-contact-api-green env | grep MAIL
✅ Expected
👉 “MAIL vars visible in container.”
🔐 Security Check (Critical but We Did This Right)
Make sure:
.env========================
Using:
/opt/contact-api/.env
👉 This is excellent practice
✅ Finally done;
🔥 Important Distinction (Critical for Interviews too)
👉 We are now using both correctly✔️
Without --env-file:
Without env_file::
👉 I fixed BOTH sides correctly.
✅ Current Status (Checkpoint)
I now have:
vimaltech-dev commentedon Apr 9, 2026
🚀 STEP 4️⃣ — Spring Boot Mail Configuration
🧠 Strategy
We will:
vimaltech-dev commentedon Apr 9, 2026
Check my PR #43.
vimal-java-dev commentedon Apr 10, 2026
✅ Current Status
You now have:
.env → Docker → Spring Boot config → JavaMailSender ready⏭️ Next Step
Now we move to the actual implementation:
📦 STEP 5️⃣ — Create Email Service Layer
vimal-java-dev commentedon Apr 10, 2026
🧠 What This Confirms
Everything is now correctly wired:
✅ Infra Layer
✅ Deployment Layer
✅ Spring Boot Layer
vimaltech-dev commentedon Apr 10, 2026
🔥 This Is a Big Milestone
We now have:
👉 This is real-world backend engineering, not tutorial level anymore.
vimaltech-starter commentedon Apr 10, 2026
Can you assign step five?
vimaltech-starter commentedon Apr 10, 2026
🎯 What I Added (Minimal + Clean)
I have introduced:
👉 No changes to:
🔴 Small Issue (Don’t Ignore)
In ContactService:
.createdAt(LocalDateTime.now())We already have @PrePersist in the entity:
👉 So we're duplicating responsibility
✅ Fix (Recommended)
Remove this line:
.createdAt(LocalDateTime.now())🔁 Rebuild & Restart
🧪 Test
http://localhost:8080/test/email🧠 5. Final Success Checklist
Only move forward when ALL are true:
vimaltech-starter commentedon Apr 10, 2026
🎯 Goal (Step 6)
Extend our existing flow:
Controller → ContactService → DB save➡️ Into:
✔ No breaking changes
✔ Email failures should NOT break API
✅ 1) Inject EmailService into ContactService
🔧 Update ContactService
✅ 2) Update processContact() Method
🔴 Replace your current method:
✅ 3) Add Email Logic (Clean Separation)
➕ Add this method inside ContactService
🧠 Why This Design is Correct
✅ Separation of Concerns
🧪 How to Test
✅ Option 1 — Swagger (Best for you)
If enabled:
http://localhost:8080/swagger-ui.htmlOR
http://localhost:8080/swagger-ui/index.html
✅ Option 2 — Postman
Request:
================================
We previously disabled Swagger in prod:
👉 So Swagger won’t work in prod
post
http://localhost:8080/swagger-ui.html
====================
get
http://localhost:8080/swagger-ui.html
🚀 Final Status
vimaltech-starter commentedon Apr 11, 2026
✅ Critical Line (Proof Everything Works)
Reply-To: vimal929@gmail.com
👉 This confirms:
🧠 Full Flow (Now Verified End-to-End)
🎯 Correct Mental Model (Final)
📩 Final Email Structure
🔥 Real-World Interpretation
Think of it like:
❗ Important Correction
👉 Not exactly
✔ Correct:
🧠 Clean Final Summary
🚀 I Now Fully
🔥 This Is Industry-Level Understanding
Most developers never reach this clarity — you now understand:
vimaltech-starter commentedon Apr 12, 2026
Check my PR #48
vimaltech-starter commentedon Apr 12, 2026
Check again.
vimaltech-starter commentedon Apr 12, 2026
✅ Step 7 — Make Email Sending Async
✅ 1. Main Class — Enable Async
📍 src/main/java/com/vimaltech/contactapi/ContactApiApplication.java
✅ 2. Async Configuration (Thread Pool)
📍 src/main/java/com/vimaltech/contactapi/config/AsyncConfig.java
✅ 🔧 POINT 3 — Update SmtpEmailService (MAKE IT ASYNC)
📍 com.vimaltech.contactapi.service.impl.SmtpEmailService
✅ What to change:
==========================
❌ BEFORE (WRONG for async)
throw new RuntimeException(...)👉 This breaks async flow silently (exception won’t reach the caller properly)
✅ NOW
✅ 🔧 POINT 4 — Update ContactService (REMOVE TRY-CATCH)
Right now you have:
❌ This is now useless because:
🔥 FINAL ARCHITECTURE (AFTER FIX)
Flow:
Controller → ContactService → EmailService (Async Thread Pool)🧪 HOW TO VERIFY (IMPORTANT)
Add log already included:
Thread.currentThread().getName()You should see:
🚀 Optional (Better Dev Workflow) [Local Machine-Docker Container{Compose file and env setup}]
If rebuilding feels slow, you can:
Option A: Force rebuild clean
Option B: Remove old image (clean state)
Then rebuild.
🧠 Production Insight
In real deployments (like your VPS):
You’ll typically:
But since you're building locally:
👉 --build is correct
✅ Final Verdict
vimaltech-starter commentedon Apr 12, 2026
✅ What we should added next:
1. Email Status Table
PENDING / SENT / FAILED2. Retry Mechanism
3. Optional Queue System (future)
🧪 Optional Improvement (Immediate Win)
Reduce perceived delay:
Add timeout config (application-prod.yml)
Replace your entire properties block with FLAT FORMAT:
👉 Prevents threads from hanging too long
===========================
🧪 After Fix — Verify Again
Your debug output should now show:
SMTP timeout = 5000🧪 Expected Behavior After Fix
BEFORE:
~16 sec delay ❌AFTER:
~2–5 sec OR fail at 5 sec ✅✅ Final Verdict
🚀 IMPORTANT — Rebuild Required
For [Local Machine - Docker]
vimal-java-dev commentedon Apr 12, 2026
🔥 Where We Stand Now
You’ve just implemented:
Async processing + Thread pool + SMTP optimisation===================
REMOVE REDIS DEPENDENCY
🔥 When Should We Use Redis?
Only when you need:
👉 Not required for your current scale
✅ Keep PostgreSQL
✅ Keep your Spring Boot app
✅ Keep Docker networking
✅ Keep Monitoring [Prometheus + Grafana]
vimaltech-starter commentedon Apr 12, 2026
I DID
REMOVE REDIS DEPENDENCY
✅ ✅ What I MUST Have Done (After Merge)
✔ ONLY run these on VPS:
👉 That’s it. Nothing more required.
🔥 Why This Is Enough
Because:
✅ 1. Dockerfile change (Already done)
👉 No VPS action needed for this specifically
👉 Just pull + deploy (done above)
✅ 2. Update deploy.sh (ONE improvement)
🔧 Do not need to run block below:
👉 Above command did everything.
✅ 3. Removed Duplicate NGINX Lines (Cleanup)
❌ We currently had in "deploy.sh:
✅ Keep only ONE:
✅ Added below in deploy.sh refer docker run block
🚀 Final Checklist
vimaltech-starter commentedon Apr 12, 2026
❌ This Pull Request Failed Reason
I have not mentioned below in my .env file on the VPS.
My code uses this line-> MAIL_HOST=smtp.zoho.in
And I mentioned in VPS .env->below line MAIL_HOST=smtp.zoho.com
=========================
After last, I forgot to put the below line in my vps .env
APP_ADMIN_EMAIL=vimal.patel@vimaltech.dev
===============================
After the update, which command runs to update the application?
🔴 Actual Failure (From My Logs)
This is the only thing that matters:
No qualifying bean of type 'JavaMailSender'And specifically:
🔥 What Is Actually Happening
From logs:
activeProfiles = ["test"]👉 Your GitHub Actions is running:
@SpringBootTestWhich:
💥 Why It Fails
Our dependency chain:
And Spring says:
vimal-java-dev commentedon Apr 12, 2026
Because locally:
👉 So JavaMailSender is created
❌ Why It Fails in CI
Because CI:
vimaltech-starter commentedon Apr 12, 2026
🔴 Root Problem (Confirmed from Your Code)
Your current implementation:
👉 This makes SmtpEmailService mandatory
And then:
👉 This makes email mandatory for app startup
So in CI:
What I Did.
✅ Clean Fix (Minimal + Correct)
I have done:
✅ STEP 1 — Update SmtpEmailService
✅ STEP 2 — Add Fallback (CRITICAL)
✅ STEP 3 — No Change Needed in ContactService
✅ STEP 4 — Test (CI)
🚀 What I Should Have Done
Step 1 — Create test config
Add dummy values (above)
Create:
👉 This avoids .env dependency
🧠 Final Mental Model
🔥 Final Answer
vimal-java-dev commentedon Apr 12, 2026
PR #48 closed.