Spring Boot 4 Microservices Project - Home Energy Tracker - Alert Service & Mailpit SMTP (Part 7)
This chapter covers the creation of the standalone Alert Service within our Home Energy Tracker microservices architecture. We build a Kafka consumer that reads energy usage alerts from a Kafka topic, uses Spring Boot's Java Mail Sender integrated with Mailpit (running via Docker) to dispatch simulation emails, and logs successful or failed attempts into a MySQL auditing table using Spring Data JPA and Flyway migrations.
1. Why Do We Need an Alert Service?
- Decoupled Notification Pipeline: The
usage serviceaggregates energy consumption and detects threshold breaches. Rather than handling SMTP dispatches inline (which would block usage aggregation and create tight coupling), it publishes alert payloads onto a Kafka topic (energy alerts). - Auditability and Reliability: In production systems, simply dispatching an email is insufficient. We need an immutable record (
alerttable) to track whether an alert was successfully delivered or failed due to an SMTP timeout or credential glitch. - Asynchronous Processing: Decoupling the notification consumer allows the system to scale independently. If email delivery spikes, the alert service can process messages at its own pace without impacting usage ingestion rates.
2. Core Architecture & Mental Model
Real-Life Analogy: The Automated Utility Notification System
Think of the usage service as an automated smart meter reader that flags households exceeding their monthly power quota. When a threshold is crossed, the meter reader drops a ticket into a shared mail bucket (the Kafka Topic).
The Alert Service acts as the mail room clerk who picks up the ticket, looks up the customer's email address, prints the notification letter, and drops it into the outgoing mail tray (Mailpit SMTP server). Finally, the clerk logs into a ledger book (MySQL Database) to write down whether the letter was successfully mailed or if it bounced back.
End-to-End Alert Flow
3. Project Initialization (Spring Boot 4)
We create a new Maven module named alert-service using Spring Initializr with Spring Boot 4.0.1 and Java 21.
Selected Dependencies (pom.xml)
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring for Apache Kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<!-- Spring Data JPA for MySQL audit logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Java Mail Sender for SMTP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Jackson DataType for Java 8+ Date/Time (Crucial for LocalDateTime) -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
Note: As emphasized in the lecture, Spring Boot 4 removes the mandatory requirement to explicitly define
spring.jpa.database-platform(dialect) inapplication.properties, as Hibernate automatically infers the optimal dialect for MySQL.
4. Configuring the Kafka Consumer & Application Properties
Inside alert-service/src/main/resources/application.properties, we configure the server port (8084), Kafka consumer deserialization properties, MySQL connection parameters, and local SMTP settings for Mailpit.
# Server Port
server.port=8084
# Spring Datasource (MySQL)
spring.datasource.url=jdbc:mysql://localhost:3306/home_energy_tracker_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# JPA / Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# Kafka Consumer Configuration
spring.kafka.consumer.bootstrap-servers=localhost:9092
spring.kafka.consumer.group-id=alert-service
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=*
spring.kafka.consumer.properties.spring.json.value.default.type=com.lejourney.kafka.event.AlertingEvent
# Mailpit SMTP Configuration (Dev Environment)
spring.mail.host=localhost
spring.mail.port=1025
spring.mail.properties.mail.smtp.auth=false
spring.mail.properties.mail.smtp.starttls.enable=false
5. Replicating the Kafka Event DTO
To properly deserialize incoming messages from the energy-alerts Kafka topic, the event class package structure and fields must mirror the producer (usage-service) exactly.
AlertingEvent.java
package com.lejourney.kafka.event;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class AlertingEvent {
private Long userId;
private Double messageThreshold;
private Double energyConsumed;
private String email;
}
6. Building the Kafka Consumer Service
The AlertService class listens to the energy-alerts Kafka topic, extracts the payload, formats the alert details, and delegates to the EmailService.
AlertService.java
package com.lejourney.service;
import com.lejourney.kafka.event.AlertingEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class AlertService {
private final EmailService emailService;
@KafkaListener(topics = "energy-alerts", groupId = "alert-service")
public void energyUsageAlertEvent(AlertingEvent event) {
log.info("Received alerting event for user ID: {} with email: {}", event.getUserId(), event.getEmail());
String subject = "Energy Usage Alert for User " + event.getUserId();
String message = String.format(
"Threshold Exceeded!\nThreshold: %.2f kWh\nEnergy Consumed: %.2f kWh",
event.getMessageThreshold(),
event.getEnergyConsumed()
);
emailService.sendEmail(event.getEmail(), subject, message, event.getUserId());
}
}
7. Simulating SMTP in Dev with Mailpit & Docker
To test email delivery locally without configuring a production SMTP relay (like SendGrid or AWS SES), we use Mailpit, a fast, low-memory email testing tool with an embedded SMTP server and Web UI.
Updating docker-compose.yml
Add the Mailpit container service to your root orchestration file:
version: '3.8'
services:
# ... existing services (mysql, zookeeper, kafka) ...
mailpit:
image: axllent/mailpit:latest
container_name: mailpit
ports:
- "8025:8025" # Web UI port
- "1025:1025" # SMTP server port
Spin up Docker Compose
Run the following command in your terminal to pull and start Mailpit:
docker compose up -d
- Access the Mailpit Web UI in your browser at:
http://localhost:8025
8. Database Auditing: Flyway Migration & JPA Entity
We maintain an audit log of all sent and failed email alerts in our MySQL database.
Flyway Migration (V3__alert_table.sql in user-service)
CREATE TABLE alert (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
sent BOOLEAN NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
JPA Entity (Alert.java in alert-service)
package com.lejourney.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "alert")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Alert {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private Boolean sent;
private LocalDateTime createdAt;
}
Spring Data JPA Repository (AlertRepository.java)
package com.lejourney.repository;
import com.lejourney.entity.Alert;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AlertRepository extends JpaRepository<Alert, Long> {
// Spring Data JPA automatically implements standard CRUD and flush operations
}
9. Implementing the Email Service & Audit Logic
The EmailService attempts to dispatch the email via JavaMailSender. Regardless of success or failure, it records an audit entry in the MySQL alert table.
EmailService.java
package com.lejourney.service;
import com.lejourney.entity.Alert;
import com.lejourney.repository.AlertRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@Slf4j
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender mailSender;
private final AlertRepository alertRepository;
public void sendEmail(String to, String subject, String body, Long userId) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setFrom("no-reply@lejourney.com");
message.setSubject(subject);
message.setText(body);
try {
mailSender.send(message);
log.info("Email successfully sent to {}", to);
saveAlertAudit(userId, true);
} catch (MailException e) {
log.error("Failed to send email to {}: {}", to, e.getMessage());
saveAlertAudit(userId, false);
}
}
private void saveAlertAudit(Long userId, boolean sentStatus) {
Alert alert = Alert.builder()
.userId(userId)
.sent(sentStatus)
.createdAt(LocalDateTime.now())
.build();
alertRepository.saveAndFlush(alert);
}
}
10. End-to-End Testing & Verification
Running the Microservices Ecosystem
To test the complete reactive pipeline, spin up services in the following order:
- MySQL & Kafka Infrastructure (via Docker Compose)
- User Service (Applies Flyway migration
V3__alert_table.sql) - Device Service
- Ingestion Service
- Usage Service (Evaluates scheduled energy thresholds and publishes Kafka events)
- Alert Service (Consumes events, sends email via Mailpit, and saves audit logs)
Verification via Mailpit UI
- Navigate to
http://localhost:8025in your browser. - Observe incoming alerts formatted with user thresholds and energy consumption metrics.
Verification via MySQL Workbench (Database Audit & Joins)
You can query the alert table directly or perform a JOIN with the user table to verify which user received which alert:
SELECT
u.id AS user_id,
u.username,
a.sent,
a.created_at
FROM home_energy_tracker_db.user u
JOIN home_energy_tracker_db.alert a ON a.user_id = u.id;
Summary and Key Takeaways
- Event-Driven Architecture: Decoupling threshold detection from notification delivery via Kafka ensures fault tolerance and asynchronous scalability.
- Spring Boot 4 Configuration: Leveraging modern Spring Boot configuration standards without redundant Hibernate dialect declarations.
- Local SMTP Simulation: Using Mailpit via Docker eliminates external dependencies and costs during local development and testing.
- Reliable Auditing Pattern: Every email dispatch attempt (success or failure) is immutably recorded in MySQL using Spring Data JPA (
saveAndFlush). - Jackson Java 8 Time Support: Including
jackson-datatype-jsr310is essential for properly serializing and deserializing JavaLocalDateTimeobjects across microservice boundaries. - Relational Integrity: Auditing tables link directly to user records, enabling comprehensive relational queries and historical reporting in MySQL.