Content ITV PRO
This is Itvedant Content department
Business Scenario
In this lab, you will enhance the Medicare application by implementing the Email using the JavaMailSender
Pre-Lab Preparation
Module:
1) RestApi
Phase 14: Email Confirmation
Patient pays
↓
Razorpay verification succeeds
↓
Appointment
CONFIRMED + PAID
↓
PaymentService
↓
EmailService
↓
@Async
↓
Confirmation email sent
Payment success MUST NOT depend
on email success.
Appointment stays PAID + CONFIRMED
Email failure is logged
That is exactly what we want.
Add Mail Dependency
1
<!-- Email -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
No version is needed because Spring Boot manages it.
Then:
Maven
↓
Update Project
Enable Async
2
package com.medicare;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class MedicareBackendApplication {
public static void main(String[] args) {
SpringApplication.run(
MedicareBackendApplication.class,
args
);
}
}
MedicareBackendApplication.java
public static void main(String[] args) {
SpringApplication.run(
MedicareBackendApplication.class,
args
);
}
}
@EnableAsync enables methods annotated with:
@Async
Spring Boot can supply the task executor automatically, so we do not need another executor configuration for this project. Home
I highly recommend creating a new, dedicated Gmail account (like medicare.project.test@gmail.com) specifically for this project.
While you can use your personal email, creating a separate one is much better for a few reasons:
Security: If you accidentally upload your code to GitHub with the password still in the configuration, your personal email and data won't be compromised.
Cleanliness: Every time someone books an appointment, this email will send a confirmation. Using a new account keeps your personal "Sent" folder free of hundreds of test emails.
Professionalism: It looks much better in a portfolio or demo if the email comes from medicare.noreply@... rather than your personal email address.
Here is exactly what you should do:
Create the New Gmail: Make a quick, free Google account for the project.
Turn on 2-Step Verification: Go to Google Account Security settings for this new account and enable 2-Step Verification (this is strictly required to get an App Password).
Generate an App Password: In the Security settings, search for "App Passwords." Generate a new one (it will be a 16-letter code with no spaces).
Configure your App:
MAIL_USERNAME = The new project Gmail address you just created.
MAIL_PASSWORD = The 16-letter App Password you generated (NOT the normal login password).
This gives you a completely safe, isolated environment to test the email feature.
Configure Email
3
Add this to application.properties:
# -------------------------------------------
# EMAIL
# -------------------------------------------
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=${MAIL_USERNAME}
spring.mail.password=${MAIL_PASSWORD}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
# Avoid waiting forever if mail server fails
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000
Environment Variables
4
Run
↓
Run Configurations
↓
Spring Boot App
↓
Environment
MAIL_USERNAME
MAIL_PASSWORD
MAIL_USERNAME
medicare.demo@gmail.com
Add:
Example:
For Gmail SMTP, do not put your normal Google account password into the application.
If you're using a Google account with 2-Step Verification and app passwords are available, Google supports using a 16-digit app password for applications that cannot use Sign in with Google. Google Help
MAIL_PASSWORD
= Gmail App Password
Your normal Gmail password
So:
not:
Google notes that app passwords require 2-Step Verification and may not be available for some organization-managed or Advanced Protection accounts. Google Help
Create EmailService.java
5
src/main/java/com/medicare/service/EmailService.java`
Use this complete version:
package com.medicare.service;
import com.medicare.dto.AppointmentResponse;
import com.medicare.entity.TimeSlot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class EmailService {
private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
private final JavaMailSender mailSender;
@Value("${spring.mail.username}") private String fromEmail;
public EmailService(JavaMailSender mailSender) {
this.mailSender = mailSender;
}
// -------------------------------------------------
// APPOINTMENT CONFIRMATION EMAIL
// -------------------------------------------------
@Async
public void sendAppointmentConfirmation(String patientEmail, AppointmentResponse appointment) {
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(fromEmail);
message.setTo(patientEmail);
message.setSubject("Medicare Appointment Confirmation");
message.setText(buildAppointmentEmail(appointment));
mailSender.send(message);
logger.info("Appointment confirmation email sent for appointment {}", appointment.getId());
} catch (MailException exception) {
/*
* Email failure must NOT
* reverse successful payment.
*/
logger.error("Unable to send confirmation email for appointment {}: {}", appointment.getId(),
exception.getMessage());
}
}
// -------------------------------------------------
// EMAIL BODY
// -------------------------------------------------
private String buildAppointmentEmail(AppointmentResponse appointment) {
return """
Hello %s,
Your Medicare appointment has been confirmed successfully.
Appointment Details
Appointment ID: %d
Doctor: %s
Specialization: %s
Date: %s
Time: %s
Consultation Fee: ₹%s
Appointment Status: %s
Payment Status: %s
Please arrive on time for your consultation.
Thank you,
Medicare Healthcare
"""
.formatted(
appointment.getPatientName(),
appointment.getId(),
appointment.getDoctorName(),
appointment.getSpecialization(),
appointment.getAppointmentDate(),
formatTimeSlot(
appointment.getTimeSlot()
),
appointment.getConsultationFee(),
appointment.getAppointmentStatus(),
appointment.getPaymentStatus()
);}
// -------------------------------------------------
// FORMAT TIME SLOT
// -------------------------------------------------
private String formatTimeSlot(TimeSlot timeSlot) {
if (timeSlot == null) {
return "";
}
return switch (timeSlot) {
case MORNING_10_AM ->
"10:00 AM";
case AFTERNOON_2_PM ->
"2:00 PM";
case EVENING_5_PM ->
"5:00 PM";
};}}
Why @Async is on EmailService
We want this:
Payment verification
↓
Database update
↓
Call EmailService
↓
Payment response returns
↓
Email sends independently
not:
Payment verification
↓
Wait for Gmail...
↓
Wait...
↓
Wait...
↓
Finally return response
Also, EmailService is a separate Spring bean. That's important because Spring's default @Async implementation works through proxy interception; calling an @Async method on the same object would not give us the same behavior. Home
Modify PaymentService
6
Inside PaymentService:
private final EmailService emailService;
private final AppointmentRepository appointmentRepository;
private final UserRepository userRepository;
private final AppointmentService appointmentService;
private final EmailService emailService;
Update PaymentService Constructor
7
Change:
public PaymentService(
AppointmentRepository appointmentRepository,
UserRepository userRepository,
AppointmentService appointmentService) {
this.appointmentRepository = appointmentRepository;
this.userRepository =userRepository;
this.appointmentService =appointmentService;
}
to:
public PaymentService(
AppointmentRepository appointmentRepository,
UserRepository userRepository,
AppointmentService appointmentService,
EmailService emailService) {
this.appointmentRepository =appointmentRepository;
this.userRepository = userRepository;
this.appointmentService = appointmentService;
this.emailService = emailService;
}
Spring injects it automatically.
Update Successful Verification Section
8
In PaymentService.verifyPayment(), you currently end with something like:
appointmentRepository
.save( appointment );
return appointmentService
.getAppointmentById(appointmentId,email );
Replace only this final section with:
appointmentRepository
.save(appointment );
AppointmentResponse
appointmentResponse = appointmentService
.getAppointmentById(
appointmentId,
email);
emailService.sendAppointmentConfirmation(
patient.getEmail(),
appointmentResponse );
return appointmentResponse;
That's it.
First Email Test
9
MAIL_USERNAME MAIL_PASSWORD
1. Login as PATIENT 2. Book appointment 3. Appointment becomes: PENDING + UNPAID 4. Click Pay Now 5. Complete Razorpay test payment 6. Backend verifies payment 7. Appointment becomes: CONFIRMED + PAID 8. Check patient's registered email inbox
Start your backend with:
configured.
Then:
Subject:
Medicare Appointment Confirmation
Hello Rahul Shah,
Your Medicare appointment has been confirmed successfully.
Appointment Details
Appointment ID: 12
Doctor: Dr. Ananya Sharma
Specialization: Cardiologist
Date: 2026-10-10
Time: 10:00 AM
Consultation Fee: ₹1200.00
Appointment Status: CONFIRMED
Payment Status: PAID
Please arrive on time for your consultation.
Thank you,
Medicare Healthcare
You should receive approximately:
Then:
Check Backend Console
10
Appointment confirmation email sent
for appointment 12
Successful email:
If SMTP fails:
Unable to send confirmation email
for appointment 12...
Database:
CONFIRMED + PAID
But importantly:
Email failure does not change payment status.
Test Duplicate Verification
POST /api/payments/12/verify
After successful payment, manually call:
again.
Because the appointment is already:
PAID
the service returns the existing AppointmentResponse.
It should not send another email.
That's correct.
Test Email Failure
MAIL_PASSWORD
Temporarily use a bad:
Then complete a payment.
Expected:
Payment:PAID
Appointment:CONFIRMED
Email:FAILED
Backend:logs error
The user should not lose their successful payment just because email delivery failed.
This is an important real application principle.
We are done with this lab. The latest source code has been uploaded to GitHub. You can access the latest commit using the link below:
Great job!
You have successfully completed the email functionality in the Medicare application.
Checkpoint
Git Push
git push origin branchNameBy Content ITV