Medicare Email Confirmation

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

 

  • The flow becomes:

   ↓

PaymentService

    ↓

EmailService

    ↓

@Async

    ↓

Confirmation email sent

 

  • The important behavior is:

Payment success MUST NOT depend

on email success.

  • If Gmail/SMTP has a temporary problem:

Appointment stays PAID + CONFIRMED

Email failure is logged

  • That is exactly what we want.

Add Mail Dependency

1

  • In pom.xml, inside <dependencies> add:
<!-- 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

  • open:
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

  • Replace it with:


    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:

  1. Create the New Gmail: Make a quick, free Google account for the project.

  2. 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).

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

Add dependency field

Inside PaymentService:

private final EmailService emailService;

Your fields become:

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 branchName