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

  1. vimaltech-dev commented on Apr 8, 2026

    @vimaltech-dev
    Contributor

    Assign me:

    Would be part of the last service.

  2. 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
  3. 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 ✅
  4. vimaltech-dev commented on Apr 9, 2026

    @vimaltech-dev
    Contributor

    Check my PR #43.

  5. 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
  6. 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
  7. 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.

  8. 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?

  9. 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
  10. 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 ✅
  11. 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)
    
  12. vimaltech-starter commented on Apr 12, 2026

    @vimaltech-starter
    Contributor

    Check my PR #48

  13. vimaltech-starter commented on Apr 12, 2026

    @vimaltech-starter
    Contributor

    Check my PR #48

    Check again.

  14. 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
  15. 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
    
  16. 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]

  17. 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
  18. 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'
    
  19. 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
  20. 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

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