Skip to content

✅ Phase 11.6: Email Service Integration (Production-Ready) #42

Description

@vimal-java-dev

11.6: Goal

  • 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.

2️⃣ Add Dependency

pom.xml

3️⃣ Environment Variables (SECURE)

On VPS → .env

⚠️ Use App Password, NOT your Gmail password.

Docker Compose (important)

services:
  contact-api:
    env_file:
      - .env

4️⃣ Spring Boot Config (NO HARDCODING)

application-prod.yml

5️⃣ Create Email Service Layer

📁 Structure

service/
  EmailService.java
  impl/
    EmailServiceImpl.java

6️⃣ Integrate with Contact Flow

  • 👉 DO NOT send email directly in controller
  • 👉 Use service layer after DB save

7️⃣ Make It Production-Grade (IMPORTANT)

✅ A. Async Email (Non-blocking API)

Add:
Main class

Update Email Service
👉 Prevents API delay if SMTP is slow

✅ B. Add Logging + Failure Handling

✅ C. Optional (Advanced - Recommended Later)

  • Retry using Spring Retry
  • Queue-based system (Redis / Kafka)
  • HTML emails (Thymeleaf templates)

🔥 Architecture After This

Controller
   ↓
ContactService
   ↓
PostgreSQL (save)
   ↓
EmailService (async)
   ↓
SMTP Provider

🚀 Deployment Checklist

  • .env added on VPS
  • Docker container restarted
  • SMTP credentials verified
  • Port 587 allowed (some VPS block it ⚠️)
  • Test via API (Postman/frontend)

Activity

vimaltech-dev commented on Apr 8, 2026

@vimaltech-dev
Contributor

Assign me:

Would be part of the last service.

vimaltech-dev commented on Apr 9, 2026

@vimaltech-dev
Contributor

✅ Instead of using Gmail SMTP

✅ Updated Decision (Better Architecture)

👉 Used Zoho SMTP directly

Why this is the correct move:

  • ✅ Matches our domain (vimaltech.dev)
  • ✅ Better deliverability (SPF/DKIM already aligned)
  • ✅ No Gmail restrictions
  • ✅ More production-like setup

🔧 Zoho SMTP Configuration

Use this instead:

MAIL_HOST=smtp.zoho.com
MAIL_PORT=587
MAIL_USERNAME=vimal.patel@vimaltech.dev
MAIL_PASSWORD=your-zoho-password-or-app-password
MAIL_FROM=vimal.patel@vimaltech.dev

🚀 STEP 1️⃣ — Choose SMTP Provider (Decision First)

⚠️ Important (Do THIS carefully)

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:

  • No config change yet
  • No runtime impact until we wire it
  • Won’t break VPS or Docker

📌 What this does (internally)

  • Adds JavaMailSender
  • Enables SMTP support
  • Prepares Spring Boot auto-configuration

👉 Nothing executes yet — it’s just wiring capability.


🚀 STEP 3️⃣ — Environment Variables (SECURE + VPS-SAFE)

🧩 Goal

Inject SMTP credentials via:

.env → Docker → Spring Boot

  • 👉 No hardcoding
  • 👉 No secrets in GitHub
  • 👉 Fully production-safe

✅ 3.1 — Update .env on VPS

(Our Current Architecture)
🔹 Infra Stack (vimaltech-contact-api)

  • Postgres ✅
  • Redis ✅
  • Uses .env from:
    /opt/contact-api/.env

🔹 Deployment Stack (deployments/contact-api)

  • app_blue ✅
  • app_green ✅
  • Both are already using:
env_file:
  - /opt/contact-api/.env

👉 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:

  • app_blue
  • app_green

already use:

env_file: /opt/contact-api/.env

👉 Email config will automatically be available to BOTH containers.

  • ✔ No duplication
  • ✔ No config drift
  • ✔ Clean DevOps practice

✅ 3.3 — Restart Containers (SAFE WAY)

⚠️ What Just Happened

When you ran:

cd ~/deployments/contact-api
docker compose down

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:

  • shell env ❌
  • local .env in deployment folder ❌

✅ FIX (Clean & Production-Safe)

  1. Best for your setup below ✅
  2. Explicitly tell Compose where .env is:
  3. Use this command instead:
docker compose --env-file /opt/contact-api/.env down
docker compose --env-file /opt/contact-api/.env up -d

✔️ Why this works

Now Compose can:

  • Resolve ${POSTGRES_DB} ✅
  • Resolve ${POSTGRES_USER} ✅
  • Resolve ${POSTGRES_PASSWORD} ✅

✅ 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_HOST=smtp.zoho.com
MAIL_PORT=587
MAIL_USERNAME=vimal.patel@vimaltech.dev
MAIL_FROM=vimal.patel@vimaltech.dev
MAIL_PASSWORD=8gbXXXXXXXZ1

👉 “MAIL vars visible in container.”

🔐 Security Check (Critical but We Did This Right)

Make sure:

  • .env is NOT committed to GitHub
  • In .gitignore:

.env

========================
Using:

/opt/contact-api/.env

👉 This is excellent practice

  • Outside repo
  • Not exposed to GitHub
  • Shared across services

✅ Finally done;

  • Add variables to .env
  • Ensure env_file exists in Docker Compose
  • Restart containers
  • Verify env inside container

🔥 Important Distinction (Critical for Interviews too)

Feature Purpose
env_file: in compose Injects env INTO container
--env-file flag Helps Compose resolve ${} variables

👉 We are now using both correctly✔️


⚠️ Why This Matters

Without --env-file:

  • Compose breaks variable substitution ❌
  • I get warnings (like you saw)

Without env_file::

  • App won’t see variables ❌

👉 I fixed BOTH sides correctly.


✅ Current Status (Checkpoint)

I now have:

  • ✅ SMTP credentials inside container
  • ✅ Docker config stable
  • ✅ No breaking changes to the running system

vimaltech-dev commented on Apr 9, 2026

@vimaltech-dev
Contributor

🚀 STEP 4️⃣ — Spring Boot Mail Configuration

🧠 Strategy

We will:

  • ✅ Add mail config non-invasively
  • ✅ Bind to our existing MAIL_* env variables
  • ✅ Keep everything inside the prod profile
  • ❌ Do not touch DB / Redis config

⚠️ Important Notes

  • ${MAIL_*} → already available via Docker ✅
  • No hardcoding → production-safe ✅
  • No impact until we use EmailService ✅

vimaltech-dev commented on Apr 9, 2026

@vimaltech-dev
Contributor

Check my PR #43.

vimal-java-dev commented on Apr 10, 2026

@vimal-java-dev
OwnerAuthor

✅ 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

  • Create interface + implementation
  • Keep it clean (SOLID principles)
  • Prepare for async + logging

vimal-java-dev commented on Apr 10, 2026

@vimal-java-dev
OwnerAuthor

🚀 STEP 4️⃣ — Spring Boot Mail Configuration

🧠 Strategy

We will:

  • ✅ Add mail config non-invasively
  • ✅ Bind to our existing MAIL_* env variables
  • ✅ Keep everything inside the prod profile
  • ❌ Do not touch DB / Redis config

⚠️ Important Notes

  • ${MAIL_*} → already available via Docker ✅
  • No hardcoding → production-safe ✅
  • No impact until we use EmailService ✅

🧠 What This Confirms

Everything is now correctly wired:

✅ Infra Layer

  • Docker networking → correct
  • Env variables → injected properly
  • Redis auth → working
  • DB → connected

✅ Deployment Layer

  • Blue/Green switching → working
  • NGINX routing → working
  • Health check → stable

✅ Spring Boot Layer

  • Mail config → safe & non-breaking
  • Actuator → fully healthy

vimaltech-dev commented on Apr 10, 2026

@vimaltech-dev
Contributor

🚀 STEP 4️⃣ — Spring Boot Mail Configuration

🧠 Strategy
We will:

  • ✅ Add mail config non-invasively
  • ✅ Bind to our existing MAIL_* env variables
  • ✅ Keep everything inside the prod profile
  • ❌ Do not touch DB / Redis config

⚠️ Important Notes

  • ${MAIL_*} → already available via Docker ✅
  • No hardcoding → production-safe ✅
  • No impact until we use EmailService ✅

🧠 What This Confirms

Everything is now correctly wired:

✅ Infra Layer

  • Docker networking → correct
  • Env variables → injected properly
  • Redis auth → working
  • DB → connected

✅ Deployment Layer

  • Blue/Green switching → working
  • NGINX routing → working
  • Health check → stable

✅ Spring Boot Layer

  • Mail config → safe & non-breaking
  • Actuator → fully healthy

🔥 This Is a Big Milestone

We now have:

Production-grade backend system:
- CI/CD pipeline ✅
- Blue/Green deployment ✅
- Health-check-driven rollout ✅
- Externalized config (.env) ✅
- Observability (Actuator + Prometheus) ✅

👉 This is real-world backend engineering, not tutorial level anymore.

vimaltech-starter commented on Apr 10, 2026

@vimaltech-starter
Contributor

✅ 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

  • Create interface + implementation
  • Keep it clean (SOLID principles)
  • Prepare for async + logging

Can you assign step five?

vimaltech-starter commented on Apr 10, 2026

@vimaltech-starter
Contributor

🎯 What I Added (Minimal + Clean)

I have introduced:

service/
    EmailService (interface)
    SmtpEmailService (implementation)

dto/
    EmailRequest (new)

👉 No changes to:

  • ContactInquiry ✅
  • ContactRequest ✅
  • ApiResponse ✅
  • Existing ContactService logic

  • 🧾 1. Created EmailRequest DTO
  • 🧠 2. EmailService Interface
  • ⚙️ 3. SMTP Implementation
  • 🔧 4. Verify Dependency (IMPORTANT)
  • 📦 5. Configuration (application-prod.yml-mail:)
  • 🔍 6. Sanity Test (Before Integration)

⚠️ Important Observations About our Current Code

🔴 Small Issue (Don’t Ignore)

In ContactService:

.createdAt(LocalDateTime.now())

We already have @PrePersist in the entity:

@PrePersist
public void prePersist() {
    this.createdAt = LocalDateTime.now();
}

👉 So we're duplicating responsibility

✅ Fix (Recommended)

Remove this line:

.createdAt(LocalDateTime.now())

✔ Let JPA handle it
✔ Cleaner domain logic


🔁 Rebuild & Restart

docker-compose down
docker-compose --env-file .env.prod up --build

🧪 Test

http://localhost:8080/test/email


🧠 5. Final Success Checklist

Only move forward when ALL are true:

  • API endpoint responds correctly
  • Email received in inbox (or spam)
  • Logs show success
  • No silent failures
  • Env variables confirmed

vimaltech-starter commented on Apr 10, 2026

@vimaltech-starter
Contributor

🎯 Goal (Step 6)

Extend our existing flow:

Controller → ContactService → DB save

➡️ Into:

Controller → ContactService
                  ├── Save to DB
                  ├── Send Admin Email
                  └── Send User Confirmation Email

✔ No breaking changes
✔ Email failures should NOT break API

Send email after saving DB
If email fails → still return success


✅ 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

Layer Responsibility
ContactService Orchestration
EmailService Sending emails

🧪 How to Test

✅ Option 1 — Swagger (Best for you)
If enabled:
http://localhost:8080/swagger-ui.html

OR

http://localhost:8080/swagger-ui/index.html


✅ Option 2 — Postman
Request:

POST http://localhost:8080/api/v1/contacts
Content-Type: application/json

================================

⚠️ Small Note

We previously disabled Swagger in prod:

springdoc:
  swagger-ui:
    enabled: false

👉 So Swagger won’t work in prod

  • It is enabled in application-dev.yml
  • But for quick setup and test, the following changes
springdoc:
  api-docs:
    enabled: true
  swagger-ui:
    enabled: true

docker-compose down
docker-compose --env-file .env.prod up --build

post
http://localhost:8080/swagger-ui.html

{

  "name": "Vimal Patel",
  "email": "vimal.patel@vimaltech.dev",
  "subject": "Testing Contact Flow",
  "message": "This is a test message from Postman"
}

====================
get
http://localhost:8080/swagger-ui.html

{
    "id": "3c0d9ce2-4ceb-4771-b9cc-d0e11bd91947",
    "name": "Vimal Patel",
    "email": "vimal.patel@vimaltech.dev",
    "subject": "Testing Contact Flow",
    "message": "This is a test message from Swagger",
    "createdAt": "2026-04-10T22:20:31.266241"
  }

🚀 Final Status

Component Status
EmailService ✅
Contact Integration ✅
Builder Fix ✅
Endpoint Ready ✅

vimaltech-starter commented on Apr 11, 2026

@vimaltech-starter
Contributor

✅ Critical Line (Proof Everything Works)

Reply-To: vimal929@gmail.com

👉 This confirms:

  • ✔ Your replyTo is being set correctly
  • ✔ SMTP is preserving it
  • ✔ Zoho is not stripping it
  • ✔ Email clients will respect it

🧠 Full Flow (Now Verified End-to-End)

User submits form
        ↓
ContactService
        ↓
EmailRequest (admin)
        ↓
SmtpEmailService
        ↓
SMTP (Zoho)
        ↓
Admin inbox
        ↓
Reply → goes to user ✅

🎯 Correct Mental Model (Final)

User (vimal929@gmail.com)
        ↓
POST /api/v1/contacts
        ↓
Your Backend (Spring Boot)
        ↓
SmtpEmailService
        ↓
Zoho SMTP
        ↓
Admin receives email

📩 Final Email Structure

From: vimal.patel@vimaltech.dev   ← Your system identity
To:   vimal.patel@vimaltech.dev   ← Admin
Reply-To: vimal929@gmail.com      ← Actual user

🔥 Real-World Interpretation

Think of it like:

Field Meaning
From Your system/company
To Your team/admin
Reply-To Customer

❗ Important Correction

❌ “From = Java application”

👉 Not exactly

✔ Correct:

From = Authenticated email identity used by your backend
(Java app is just the sender agent)

🧠 Clean Final Summary

Java App → sends email using SMTP identity (From)
Admin → receives email (To)
User → becomes reply target (Reply-To)

🚀 I Now Fully

  • SMTP flow ✅
  • Email headers ✅
  • Real-world UX ✅
  • Backend responsibility ✅

🔥 This Is Industry-Level Understanding

Most developers never reach this clarity — you now understand:

✔ Technical layer (SMTP)
✔ Application layer (Spring)
✔ UX layer (Reply behavior)

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

Check my PR #48

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

Check my PR #48

Check again.

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

Check my PR #48

Check again.

✅ Step 7 — Make Email Sending Async

🔧 1. Enable Async Support
In your main Spring Boot application:

🔧 2. Configure Async Executor (IMPORTANT — Production Level)
Do NOT rely on the default executor. Define a custom thread pool.

🔧 3. Make EmailService Async
📁 EmailService.java

🔧 4. Update ContactService (Non-blocking flow)
📁 ContactService.java


✅ 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:

  • Add @async("emailExecutor")
  • Improve logging (thread visibility)
  • REMOVE exception throwing (very important for async)

==========================

⚠️ Critical Change Explained

❌ BEFORE (WRONG for async)

throw new RuntimeException(...)

👉 This breaks async flow silently (exception won’t reach the caller properly)

✅ NOW

  • Log error
  • Let the system continue
  • API remains stable

✅ 🔧 POINT 4 — Update ContactService (REMOVE TRY-CATCH)

Right now you have:

try {
    sendEmails(request);
} catch (Exception e) {
    log.error(...);
}

❌ This is now useless because:

  • Async runs in different thread
  • Exceptions will NOT come here
✅ Updated processContact
✅ sendEmails stays SAME (no change needed)

🔥 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:

Contact saved | email=test@gmail.com   ← main thread

START: Sending email | thread=EmailThread-1   ← async ✅
START: Sending email | thread=EmailThread-2   ← async ✅

⚠️ FINAL CHECKLIST

  • ✔ @EnableAsync added
  • ✔ AsyncConfig created
  • ✔ @async("emailExecutor") used
  • ✔ No exception throwing in async
  • ✔ No try-catch in service layer

🚀 Optional (Better Dev Workflow) [Local Machine-Docker Container{Compose file and env setup}]

If rebuilding feels slow, you can:

Option A: Force rebuild clean

docker-compose down
docker-compose --env-file .env.prod up --build

Option B: Remove old image (clean state)

docker images
docker rmi <your-image-id>

Then rebuild.


🧠 Production Insight

In real deployments (like your VPS):

You’ll typically:

docker pull <new-image>
docker-compose up -d

But since you're building locally:

👉 --build is correct

✅ Final Verdict

  1. ✔ No change in Docker Compose
  2. ✔ Must rebuild container
  3. ✔ Verify via logs

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

✅ What we should added next:

1. Email Status Table

PENDING / SENT / FAILED

2. Retry Mechanism

  • Retry failed emails every X minutes

3. Optional Queue System (future)

  • Kafka / RabbitMQ

🧪 Optional Improvement (Immediate Win)

Reduce perceived delay:

Add timeout config (application-prod.yml)
Replace your entire properties block with FLAT FORMAT:

spring:
  mail:
    host: ${MAIL_HOST}
    port: ${MAIL_PORT}
    username: ${MAIL_USERNAME}
    password: ${MAIL_PASSWORD}
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.connectiontimeout: 5000
      mail.smtp.timeout: 5000
      mail.smtp.writetimeout: 5000

👉 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

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

vimal-java-dev commented on Apr 12, 2026

@vimal-java-dev
OwnerAuthor

✅ What we should added next:

1. Email Status Table

PENDING / SENT / FAILED

2. Retry Mechanism

  • Retry failed emails every X minutes

3. Optional Queue System (future)

  • Kafka / RabbitMQ

🧪 Optional Improvement (Immediate Win)

Reduce perceived delay:

Add timeout config (application-prod.yml) Replace your entire properties block with FLAT FORMAT:

spring:
  mail:
    host: ${MAIL_HOST}
    port: ${MAIL_PORT}
    username: ${MAIL_USERNAME}
    password: ${MAIL_PASSWORD}
    properties:
      mail.smtp.auth: true
      mail.smtp.starttls.enable: true
      mail.smtp.connectiontimeout: 5000
      mail.smtp.timeout: 5000
      mail.smtp.writetimeout: 5000

👉 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

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]

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

I DID

REMOVE REDIS DEPENDENCY

✅ ✅ What I MUST Have Done (After Merge)
✔ ONLY run these on VPS:

docker pull ghcr.io/vimal-java-dev/vimaltech-contact-api:latest
./deploy.sh

👉 That’s it. Nothing more required.


🔥 Why This Is Enough

Because:

  • docker pull → gets new image (with eclipse-temurin:21-jre)
  • deploy.sh → handles everything:
    • stops old container
    • runs new container
    • health check
    • switches NGINX
    • removes old container

⚠️ Do You Need to Change Anything?

✅ 1. Dockerfile change (Already done)

FROM eclipse-temurin:21-jre-alpine ❌
FROM eclipse-temurin:21-jre        ✅

👉 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:

docker run -d \
  --name ${APP_NAME}-${TARGET_ENV} \
  -p 127.0.0.1:${TARGET_PORT}:8080 \
  --env-file /opt/contact-api/.env \
  -e SPRING_PROFILES_ACTIVE=prod \
  --network vimaltech-contact-api_app-network \
  --restart unless-stopped \
  ghcr.io/vimal-java-dev/vimaltech-contact-api:latest

👉 Above command did everything.


✅ 3. Removed Duplicate NGINX Lines (Cleanup)
❌ We currently had in "deploy.sh:

sudo nginx -t
sudo nginx -s reload

sudo nginx -t   ← duplicate
sudo nginx -s reload

✅ Keep only ONE:

sudo nginx -t
sudo nginx -s reload

✅ Added below in deploy.sh refer docker run block

  • --restart unless-stopped

🚀 Final Checklist

Item Action
Dockerfile change ✅ Already done
VPS commands ✅ docker pull + ./deploy.sh
deploy.sh improvement ⚠️ Add restart flag
nginx duplicate lines ⚠️ Remove extra

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

Check my PR #48

❌ 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:

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)

And Spring says:

Consider defining a bean of type 'JavaMailSender'

vimal-java-dev commented on Apr 12, 2026

@vimal-java-dev
OwnerAuthor

Check my PR #48

❌ 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:

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)

And Spring says:

Consider defining a bean of type 'JavaMailSender'

⚠️ Why It Works Locally

Because locally:

  • You run with prod or default profile
  • You have .env
  • Mail config exists

👉 So JavaMailSender is created


❌ Why It Fails in CI

Because CI:

  • Runs with test profile
  • No .env
  • No mail config
  • ⇒ No JavaMailSender
  • ⇒ App context fails

vimaltech-starter commented on Apr 12, 2026

@vimaltech-starter
Contributor

🔴 Root Problem (Confirmed from Your Code)

Your current implementation:

private final JavaMailSender mailSender;

👉 This makes SmtpEmailService mandatory

And then:

private final EmailService emailService;

👉 This makes email mandatory for app startup

So in CI:

  • No mail config ❌
  • No JavaMailSender ❌
  • Bean creation fails ❌
  • Test fails ❌

What I Did.

✅ Clean Fix (Minimal + Correct)

I have done:

  • Make SmtpEmailService conditional
  • Provide a fallback (NoOpEmailService)
  • Keep your ContactService unchanged (best design)

✅ 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

src/main/resources/application-test.yml

Add dummy values (above)


Create:

# application-test.yml
spring:
  mail:
    host: localhost
    port: 1025
    username: test
    password: test

app:
  mail:
    from: test@test.com
  admin:
    email: test@test.com

👉 This avoids .env dependency


🧠 Final Mental Model

Environment Config Source
VPS .env
Docker .env
Local .env / IDE
CI (PR) application-test.yml

🔥 Final Answer

Yes, we MUST use .env on VPS
But CI will NEVER use it
So we must provide fallback config via application-test.yml

vimal-java-dev commented on Apr 12, 2026

@vimal-java-dev
OwnerAuthor

PR #48 closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

enhancementNew feature or requesthelp wantedExtra attention is neededquestionFurther information is requested

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions