diff --git a/EMAIL_CONFIGURATION.md b/EMAIL_CONFIGURATION.md
deleted file mode 100644
index 1e9ca25..0000000
--- a/EMAIL_CONFIGURATION.md
+++ /dev/null
@@ -1,177 +0,0 @@
-# Email Configuration Guide for Notification Service
-
-## Development Mode (Default)
-
-By default, the service runs with the **dev profile** which **disables email health checks**. This prevents authentication failures during local development.
-
-### Running in Development Mode
-
-```bash
-# Using Maven
-mvn spring-boot:run
-
-# Using IDE - the dev profile is active by default
-# Just run NotificationServiceApplication.java
-```
-
-**Note:** Email sending will be attempted but won't fail the health check if credentials are invalid.
-
----
-
-## Testing Email Functionality
-
-If you want to test actual email sending during development, you need valid Gmail credentials with an App Password.
-
-### Step 1: Generate Gmail App Password
-
-1. Go to your Google Account: https://myaccount.google.com/
-2. Navigate to **Security**
-3. Enable **2-Step Verification** (if not already enabled)
-4. Go to **App Passwords**: https://myaccount.google.com/apppasswords
-5. Generate a new app password for "Mail"
-6. Copy the 16-character password
-
-### Step 2: Set Environment Variables
-
-```bash
-# Linux/Mac
-export EMAIL_USERNAME="your-email@gmail.com"
-export EMAIL_PASSWORD="your-16-char-app-password"
-
-# Windows (PowerShell)
-$env:EMAIL_USERNAME="your-email@gmail.com"
-$env:EMAIL_PASSWORD="your-16-char-app-password"
-```
-
-### Step 3: Enable Mail Health Check (Optional)
-
-In `application-dev.properties`, change:
-```properties
-management.health.mail.enabled=true
-```
-
-### Step 4: Run the Service
-
-```bash
-mvn spring-boot:run
-```
-
----
-
-## Production Mode
-
-For production deployments, use the **prod profile** with proper credentials:
-
-```bash
-# Set all required environment variables
-export SPRING_PROFILE=prod
-export DB_URL=jdbc:postgresql://prod-host:5432/notification_db
-export DB_USERNAME=prod_user
-export DB_PASSWORD=prod_password
-export EMAIL_USERNAME=noreply@techtorque.com
-export EMAIL_PASSWORD=production-app-password
-
-# Run the application
-java -jar notification-service.jar --spring.profiles.active=prod
-```
-
-**Production profile automatically:**
-- Enables mail health checks
-- Uses environment variables for sensitive data
-- Reduces logging verbosity
-- Sets JPA to validate-only mode
-
----
-
-## Troubleshooting
-
-### Issue: "Username and Password not accepted"
-
-**Cause:** Invalid Gmail credentials or regular password used instead of App Password.
-
-**Solutions:**
-1. Generate an App Password (see above)
-2. Disable mail health check: `management.health.mail.enabled=false`
-3. Use dev profile (mail health check disabled by default)
-
-### Issue: "535-5.7.8 BadCredentials"
-
-**Cause:** Gmail blocking login attempt.
-
-**Solutions:**
-1. Ensure 2-Step Verification is enabled
-2. Use an App Password, not your regular password
-3. Check if "Less secure app access" is required (deprecated by Google)
-
-### Issue: Email sending works but health check fails
-
-**Cause:** Transient connection issues or rate limiting.
-
-**Solution:** Disable health check in development:
-```properties
-management.health.mail.enabled=false
-```
-
----
-
-## Configuration Summary
-
-| Profile | Mail Health Check | Use Case |
-|---------|------------------|----------|
-| **dev** (default) | Disabled | Local development without email |
-| **prod** | Enabled | Production with valid credentials |
-
----
-
-## Quick Commands
-
-```bash
-# Development (no email credentials needed)
-mvn spring-boot:run
-
-# Development with email testing
-export EMAIL_USERNAME="your@gmail.com"
-export EMAIL_PASSWORD="app-password"
-mvn spring-boot:run
-
-# Production
-export SPRING_PROFILE=prod
-export EMAIL_USERNAME="prod@techtorque.com"
-export EMAIL_PASSWORD="prod-password"
-java -jar notification-service.jar
-```
-
----
-
-## Security Notes
-
-⚠️ **Never commit email credentials to version control!**
-
-- Always use environment variables
-- Add `.env` files to `.gitignore`
-- Use secret management in production (AWS Secrets Manager, Azure Key Vault, etc.)
-- Rotate credentials regularly
-
----
-
-## Alternative: Using MailHog for Development
-
-For local email testing without real credentials:
-
-```bash
-# Start MailHog
-docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
-
-# Update application-dev.properties
-spring.mail.host=localhost
-spring.mail.port=1025
-spring.mail.username=
-spring.mail.password=
-spring.mail.properties.mail.smtp.auth=false
-spring.mail.properties.mail.smtp.starttls.enable=false
-management.health.mail.enabled=false
-
-# Access web UI at http://localhost:8025
-```
-
-This captures all emails locally without sending them to real addresses.
diff --git a/EMAIL_FIX_SUMMARY.md b/EMAIL_FIX_SUMMARY.md
deleted file mode 100644
index b0c6a52..0000000
--- a/EMAIL_FIX_SUMMARY.md
+++ /dev/null
@@ -1,166 +0,0 @@
-# Email Configuration Quick Reference
-
-## ✅ SOLUTION IMPLEMENTED
-
-The notification service has been configured to **disable email health checks during development**, eliminating the authentication warning you encountered.
-
----
-
-## What Was Done
-
-### 1. **Disabled Mail Health Check** (Primary Solution)
-Added to `application.properties`:
-```properties
-management.health.mail.enabled=false
-```
-
-This prevents Spring Boot Actuator from attempting to connect to Gmail SMTP during health checks.
-
-### 2. **Created Development Profile**
-Created `application-dev.properties` with mail health check disabled by default.
-
-### 3. **Created Production Profile**
-Created `application-prod.properties` that enables health checks and uses environment variables.
-
----
-
-## Running the Service
-
-### Development Mode (No Email Credentials Needed)
-```bash
-cd Notification_Service/notification-service
-mvn spring-boot:run
-```
-
-✅ **No more email warnings!** The service will start cleanly.
-
-### With Real Email Testing
-```bash
-export EMAIL_USERNAME="your-email@gmail.com"
-export EMAIL_PASSWORD="your-app-password" # Gmail App Password
-mvn spring-boot:run
-```
-
-### Production Mode
-```bash
-export SPRING_PROFILE=prod
-export EMAIL_USERNAME="prod@techtorque.com"
-export EMAIL_PASSWORD="prod-password"
-java -jar notification-service.jar
-```
-
----
-
-## Understanding the Warning
-
-The warning you saw:
-```
-jakarta.mail.AuthenticationFailedException: 535-5.7.8 Username and Password not accepted
-```
-
-**Cause:** Spring Boot Actuator's health endpoint was trying to verify SMTP connection with placeholder credentials.
-
-**Impact:**
-- ⚠️ Warning in logs
-- ✅ Service still starts successfully
-- ✅ All endpoints work normally
-- ❌ Only email sending would fail (if attempted)
-
----
-
-## Configuration Files Created
-
-| File | Purpose |
-|------|---------|
-| `application.properties` | Base config with mail health disabled |
-| `application-dev.properties` | Development profile (no real email needed) |
-| `application-prod.properties` | Production profile (real credentials required) |
-| `EMAIL_CONFIGURATION.md` | Detailed setup guide |
-| `test_email_config.sh` | Verification script |
-
----
-
-## Testing Your Setup
-
-Run the verification script:
-```bash
-cd Notification_Service
-./test_email_config.sh
-```
-
-Expected output:
-```
-✓ Mail health check is DISABLED in application.properties
-✓ Development profile exists
-✓ Production profile exists
-```
-
----
-
-## Common Scenarios
-
-### Scenario 1: Local Development (Current)
-**Status:** ✅ Configured
-**Action:** None needed - just run `mvn spring-boot:run`
-**Email:** Won't send (no valid credentials)
-**Health Check:** Disabled (no warnings)
-
-### Scenario 2: Testing Email Functionality
-**Status:** Need valid Gmail App Password
-**Action:** Set EMAIL_USERNAME and EMAIL_PASSWORD env vars
-**Email:** Will send to real addresses
-**Health Check:** Keep disabled to avoid startup delays
-
-### Scenario 3: Production Deployment
-**Status:** Use prod profile
-**Action:** Set all env vars, use `--spring.profiles.active=prod`
-**Email:** Fully functional with monitoring
-**Health Check:** Enabled for monitoring
-
----
-
-## Quick Troubleshooting
-
-| Issue | Solution |
-|-------|----------|
-| Email warning on startup | ✅ Already fixed - health check disabled |
-| Need to test email | Set EMAIL_USERNAME/PASSWORD env vars |
-| Want to mock emails | Use MailHog (see EMAIL_CONFIGURATION.md) |
-| Production setup | Use prod profile + env vars |
-
----
-
-## Next Steps
-
-Your notification service is now properly configured for development!
-
-**To resume work:**
-```bash
-cd Notification_Service/notification-service
-mvn spring-boot:run
-```
-
-**To test endpoints:**
-```bash
-# Health check (should be UP without mail health)
-curl http://localhost:8088/actuator/health
-
-# API docs
-open http://localhost:8088/swagger-ui/index.html
-```
-
----
-
-## Files Modified
-
-- ✏️ `application.properties` - Added `management.health.mail.enabled=false`
-- ➕ `application-dev.properties` - New development profile
-- ➕ `application-prod.properties` - New production profile
-- ➕ `EMAIL_CONFIGURATION.md` - Detailed guide
-- ➕ `test_email_config.sh` - Verification script
-
----
-
-**Status:** ✅ **Email configuration issue RESOLVED**
-
-The service will now start without email authentication warnings during development.
diff --git a/README.md b/README.md
deleted file mode 100644
index 957a8c0..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Notification_Service
\ No newline at end of file
diff --git a/notification-service/.env.example b/notification-service/.env.example
new file mode 100644
index 0000000..b929908
--- /dev/null
+++ b/notification-service/.env.example
@@ -0,0 +1,16 @@
+# Database Configuration
+DB_HOST=localhost
+DB_PORT=5432
+DB_NAME=techtorque_notification
+DB_USER=techtorque
+DB_PASS=techtorque123
+
+# Spring Profile
+SPRING_PROFILE=dev
+
+# Email Configuration (Gmail example)
+EMAIL_USERNAME=your-email@gmail.com
+EMAIL_PASSWORD=your-app-password
+
+# Notification Configuration
+NOTIFICATION_RETENTION_DAYS=30
diff --git a/notification-service/pom.xml b/notification-service/pom.xml
index 99d0067..be6bca3 100644
--- a/notification-service/pom.xml
+++ b/notification-service/pom.xml
@@ -28,6 +28,9 @@
17
+ 1.63.0
+ 3.25.3
+ 3.1.0.RELEASE
@@ -54,6 +57,35 @@
org.springframework.boot
spring-boot-starter-actuator
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+ net.devh
+ grpc-server-spring-boot-starter
+ ${grpc.spring.boot.version}
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc.version}
+
+
+ com.google.protobuf
+ protobuf-java
+ ${protobuf.version}
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
@@ -90,7 +122,33 @@
+
+
+ kr.motd.maven
+ os-maven-plugin
+ 1.7.1
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ 0.6.1
+
+ ${project.basedir}/src/main/proto
+ com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
+
+
+
+
+ compile
+ compile-custom
+
+
+
+
org.apache.maven.plugins
maven-compiler-plugin
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/config/SecurityConfig.java b/notification-service/src/main/java/com/techtorque/notification_service/config/SecurityConfig.java
index ff37730..601b2db 100644
--- a/notification-service/src/main/java/com/techtorque/notification_service/config/SecurityConfig.java
+++ b/notification-service/src/main/java/com/techtorque/notification_service/config/SecurityConfig.java
@@ -25,7 +25,9 @@ public class SecurityConfig {
"/actuator/**",
"/health",
"/favicon.ico",
- "/error"
+ "/error",
+ "/ws/**", // Allow WebSocket endpoints
+ "/api/v1/notifications/**" // Allow notification REST API endpoints
};
@Bean
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/config/WebSocketConfig.java b/notification-service/src/main/java/com/techtorque/notification_service/config/WebSocketConfig.java
new file mode 100644
index 0000000..d239f9f
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/config/WebSocketConfig.java
@@ -0,0 +1,53 @@
+package com.techtorque.notification_service.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+/**
+ * WebSocket configuration for real-time notifications
+ * Uses STOMP (Simple Text Oriented Messaging Protocol) over WebSocket
+ *
+ * Industry standard configuration following Spring Boot best practices
+ */
+@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+
+ /**
+ * Configure message broker for handling messages
+ * - /topic: for broadcasting to multiple subscribers (pub-sub pattern)
+ * - /queue: for point-to-point messaging (user-specific)
+ * - /app: prefix for messages from client to server
+ */
+ @Override
+ public void configureMessageBroker(MessageBrokerRegistry config) {
+ // Enable simple in-memory message broker for pub-sub
+ config.enableSimpleBroker("/topic", "/queue");
+
+ // Prefix for messages from client to server
+ config.setApplicationDestinationPrefixes("/app");
+
+ // Prefix for user-specific messages
+ config.setUserDestinationPrefix("/user");
+ }
+
+ /**
+ * Register STOMP endpoints that clients will connect to
+ * - /ws/notifications: Main WebSocket endpoint
+ * - SockJS fallback for browsers that don't support WebSocket
+ * - CORS allowed for development (configure properly for production)
+ */
+ @Override
+ public void registerStompEndpoints(StompEndpointRegistry registry) {
+ registry.addEndpoint("/ws/notifications")
+ .setAllowedOriginPatterns("*") // Allow all origins for development
+ .withSockJS(); // Enable SockJS fallback for older browsers
+
+ // Plain WebSocket endpoint without SockJS (for modern clients)
+ registry.addEndpoint("/ws/notifications")
+ .setAllowedOriginPatterns("*"); // Allow all origins for development
+ }
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationApiController.java b/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationApiController.java
new file mode 100644
index 0000000..f63643a
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationApiController.java
@@ -0,0 +1,72 @@
+package com.techtorque.notification_service.controller;
+
+import com.techtorque.notification_service.dto.request.CreateNotificationRequest;
+import com.techtorque.notification_service.dto.response.ApiResponse;
+import com.techtorque.notification_service.dto.response.NotificationResponse;
+import com.techtorque.notification_service.entity.Notification;
+import com.techtorque.notification_service.service.NotificationService;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * API Controller for other services to create notifications
+ * This is separate from the user-facing NotificationController
+ */
+@RestController
+@RequestMapping("/api/v1/notifications")
+@RequiredArgsConstructor
+@Slf4j
+public class NotificationApiController {
+
+ private final NotificationService notificationService;
+ private final SimpMessagingTemplate messagingTemplate;
+
+ /**
+ * Create a notification (called by other microservices)
+ * This endpoint is whitelisted in SecurityConfig for service-to-service communication
+ */
+ @PostMapping("/create")
+ public ResponseEntity createNotification(@Valid @RequestBody CreateNotificationRequest request) {
+ log.info("Creating notification for user: {} with type: {}", request.getUserId(), request.getType());
+
+ try {
+ // Parse notification type
+ Notification.NotificationType notificationType = Notification.NotificationType.valueOf(request.getType());
+
+ // Create notification using service
+ NotificationResponse response = notificationService.createNotification(
+ request.getUserId(),
+ notificationType,
+ request.getMessage(),
+ request.getDetails()
+ );
+
+ // Send real-time notification via WebSocket
+ String destination = "/user/" + request.getUserId() + "/queue/notifications";
+ log.info("Sending WebSocket notification to destination: {}", destination);
+ messagingTemplate.convertAndSend(destination, response);
+
+ log.info("Notification created successfully for user: {}", request.getUserId());
+ return ResponseEntity.ok(ApiResponse.success("Notification created", response));
+
+ } catch (IllegalArgumentException e) {
+ log.error("Invalid notification type: {}", request.getType(), e);
+ return ResponseEntity.badRequest().body(
+ ApiResponse.builder()
+ .message("Invalid notification type: " + request.getType())
+ .build()
+ );
+ } catch (Exception e) {
+ log.error("Failed to create notification for user: {}", request.getUserId(), e);
+ return ResponseEntity.internalServerError().body(
+ ApiResponse.builder()
+ .message("Failed to create notification")
+ .build()
+ );
+ }
+ }
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationController.java b/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationController.java
index e789726..144310c 100644
--- a/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationController.java
+++ b/notification-service/src/main/java/com/techtorque/notification_service/controller/NotificationController.java
@@ -1,11 +1,13 @@
package com.techtorque.notification_service.controller;
+import com.techtorque.notification_service.dto.request.CreateNotificationRequest;
import com.techtorque.notification_service.dto.request.MarkAsReadRequest;
import com.techtorque.notification_service.dto.request.SubscribeRequest;
import com.techtorque.notification_service.dto.request.UnsubscribeRequest;
import com.techtorque.notification_service.dto.response.ApiResponse;
import com.techtorque.notification_service.dto.response.NotificationResponse;
import com.techtorque.notification_service.dto.response.SubscriptionResponse;
+import com.techtorque.notification_service.entity.Notification;
import com.techtorque.notification_service.entity.Subscription;
import com.techtorque.notification_service.service.NotificationService;
import com.techtorque.notification_service.service.SubscriptionService;
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/controller/WebSocketNotificationController.java b/notification-service/src/main/java/com/techtorque/notification_service/controller/WebSocketNotificationController.java
new file mode 100644
index 0000000..51e0d80
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/controller/WebSocketNotificationController.java
@@ -0,0 +1,90 @@
+package com.techtorque.notification_service.controller;
+
+import com.techtorque.notification_service.dto.response.NotificationResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.stereotype.Controller;
+
+/**
+ * WebSocket controller for real-time notification broadcasting
+ *
+ * Message destinations:
+ * - /app/notifications.subscribe -> Client subscribes to notifications
+ * - /topic/notifications -> Broadcast to all connected clients
+ * - /user/{userId}/queue/notifications -> Send to specific user
+ *
+ * Industry standard WebSocket messaging with STOMP protocol
+ */
+@Controller
+@RequiredArgsConstructor
+@Slf4j
+public class WebSocketNotificationController {
+
+ private final SimpMessagingTemplate messagingTemplate;
+
+ /**
+ * Handle client subscription to notification stream
+ * Clients send message to /app/notifications.subscribe
+ */
+ @MessageMapping("/notifications.subscribe")
+ @SendTo("/topic/notifications")
+ public String subscribe(@Payload String userId, SimpMessageHeaderAccessor headerAccessor) {
+ // Store userId in session attributes for user-specific routing
+ headerAccessor.getSessionAttributes().put("userId", userId);
+ log.info("User {} subscribed to WebSocket notifications", userId);
+ return "Subscription successful";
+ }
+
+ /**
+ * Send notification to specific user
+ * Called by service layer when new notification is created
+ *
+ * @param userId Target user ID
+ * @param notification Notification to send
+ */
+ public void sendNotificationToUser(String userId, NotificationResponse notification) {
+ log.info("Sending WebSocket notification to user: {} - {}", userId, notification.getMessage());
+
+ // Send to user-specific queue: /user/{userId}/queue/notifications
+ messagingTemplate.convertAndSendToUser(
+ userId,
+ "/queue/notifications",
+ notification
+ );
+ }
+
+ /**
+ * Broadcast notification to all connected users
+ * Used for system-wide announcements
+ *
+ * @param notification Notification to broadcast
+ */
+ public void broadcastNotification(NotificationResponse notification) {
+ log.info("Broadcasting WebSocket notification: {}", notification.getMessage());
+
+ // Send to topic: /topic/notifications (all subscribers receive)
+ messagingTemplate.convertAndSend("/topic/notifications", notification);
+ }
+
+ /**
+ * Send notification count update to user
+ * Called when unread count changes
+ *
+ * @param userId Target user ID
+ * @param count New unread count
+ */
+ public void sendUnreadCountUpdate(String userId, Long count) {
+ log.debug("Sending unread count update to user {}: {}", userId, count);
+
+ messagingTemplate.convertAndSendToUser(
+ userId,
+ "/queue/notifications/count",
+ count
+ );
+ }
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/dto/request/CreateNotificationRequest.java b/notification-service/src/main/java/com/techtorque/notification_service/dto/request/CreateNotificationRequest.java
new file mode 100644
index 0000000..6ba54df
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/dto/request/CreateNotificationRequest.java
@@ -0,0 +1,29 @@
+package com.techtorque.notification_service.dto.request;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CreateNotificationRequest {
+
+ @NotBlank(message = "User ID is required")
+ private String userId;
+
+ @NotBlank(message = "Type is required")
+ private String type; // INFO, WARNING, ERROR, SUCCESS
+
+ @NotBlank(message = "Message is required")
+ private String message;
+
+ private String details;
+
+ private String relatedEntityId;
+
+ private String relatedEntityType;
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/grpc/NotificationEmailGrpcService.java b/notification-service/src/main/java/com/techtorque/notification_service/grpc/NotificationEmailGrpcService.java
new file mode 100644
index 0000000..5c71329
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/grpc/NotificationEmailGrpcService.java
@@ -0,0 +1,49 @@
+package com.techtorque.notification_service.grpc;
+
+import com.techtorque.notification.grpc.NotificationEmailServiceGrpc;
+import com.techtorque.notification.grpc.SendEmailRequest;
+import com.techtorque.notification.grpc.SendEmailResponse;
+import com.techtorque.notification_service.service.TransactionalEmailService;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.devh.boot.grpc.server.service.GrpcService;
+
+/**
+ * Exposes transactional email delivery over gRPC for other services.
+ */
+@GrpcService
+@RequiredArgsConstructor
+@Slf4j
+public class NotificationEmailGrpcService extends NotificationEmailServiceGrpc.NotificationEmailServiceImplBase {
+
+ private final TransactionalEmailService transactionalEmailService;
+
+ @Override
+ public void sendTransactionalEmail(SendEmailRequest request, StreamObserver responseObserver) {
+ try {
+ log.debug("Received transactional email request for {} using template {}", request.getTo(), request.getType());
+ var result = transactionalEmailService.sendTransactionalEmail(
+ request.getTo(),
+ request.getUsername(),
+ request.getType(),
+ request.getVariablesMap());
+
+ SendEmailResponse response = SendEmailResponse.newBuilder()
+ .setStatus(result.status())
+ .setMessageId(result.messageId())
+ .setDetail(result.detail() == null ? "" : result.detail())
+ .build();
+
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ } catch (Exception ex) {
+ log.error("Unexpected error while handling transactional email request: {}", ex.getMessage(), ex);
+ responseObserver.onError(Status.INTERNAL
+ .withDescription("Failed to process transactional email request")
+ .withCause(ex)
+ .asRuntimeException());
+ }
+ }
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/seeder/DataSeeder.java b/notification-service/src/main/java/com/techtorque/notification_service/seeder/DataSeeder.java
index 2df3bd8..fe3e911 100644
--- a/notification-service/src/main/java/com/techtorque/notification_service/seeder/DataSeeder.java
+++ b/notification-service/src/main/java/com/techtorque/notification_service/seeder/DataSeeder.java
@@ -13,7 +13,6 @@
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
-import java.util.UUID;
@Component
@Profile("!test")
@@ -24,6 +23,12 @@ public class DataSeeder implements CommandLineRunner {
private final NotificationRepository notificationRepository;
private final SubscriptionRepository subscriptionRepository;
+ // Consistent user IDs matching Auth Service seed data
+ // These usernames are forwarded via X-User-Subject header from the API Gateway
+ private static final String CUSTOMER_1_ID = "customer";
+ private static final String CUSTOMER_2_ID = "testuser";
+ private static final String EMPLOYEE_1_ID = "employee";
+
@Override
public void run(String... args) {
log.info("Starting DataSeeder for Notification Service...");
@@ -44,14 +49,13 @@ public void run(String... args) {
}
private void seedNotifications() {
- log.info("Seeding notifications...");
-
- String testUserId1 = UUID.randomUUID().toString();
- String testUserId2 = UUID.randomUUID().toString();
+ log.info("Seeding notifications for consistent test users...");
+ // Use consistent user IDs that match Auth Service seed data
+ // No more random UUIDs - ensures cross-service data integrity
List notifications = Arrays.asList(
Notification.builder()
- .userId(testUserId1)
+ .userId(CUSTOMER_1_ID)
.type(Notification.NotificationType.APPOINTMENT_CONFIRMED)
.message("Your appointment has been confirmed")
.details("Appointment scheduled for tomorrow at 10:00 AM")
@@ -61,7 +65,7 @@ private void seedNotifications() {
.build(),
Notification.builder()
- .userId(testUserId1)
+ .userId(CUSTOMER_1_ID)
.type(Notification.NotificationType.SERVICE_COMPLETED)
.message("Your service has been completed")
.details("Oil change and tire rotation completed successfully")
@@ -72,7 +76,7 @@ private void seedNotifications() {
.build(),
Notification.builder()
- .userId(testUserId1)
+ .userId(CUSTOMER_1_ID)
.type(Notification.NotificationType.INVOICE_GENERATED)
.message("Invoice generated for your service")
.details("Total amount: $150.00. Payment due in 7 days.")
@@ -82,7 +86,7 @@ private void seedNotifications() {
.build(),
Notification.builder()
- .userId(testUserId2)
+ .userId(CUSTOMER_2_ID)
.type(Notification.NotificationType.APPOINTMENT_REMINDER)
.message("Appointment reminder")
.details("Your appointment is in 1 hour")
@@ -92,7 +96,7 @@ private void seedNotifications() {
.build(),
Notification.builder()
- .userId(testUserId2)
+ .userId(CUSTOMER_2_ID)
.type(Notification.NotificationType.PAYMENT_RECEIVED)
.message("Payment received")
.details("We've received your payment of $200.00")
@@ -100,43 +104,61 @@ private void seedNotifications() {
.deleted(false)
.readAt(LocalDateTime.now().minusDays(1))
.expiresAt(LocalDateTime.now().plusDays(30))
+ .build(),
+
+ Notification.builder()
+ .userId(EMPLOYEE_1_ID)
+ .type(Notification.NotificationType.APPOINTMENT_CONFIRMED)
+ .message("New appointment assigned to you")
+ .details("Customer appointment scheduled for today at 2:00 PM")
+ .read(false)
+ .deleted(false)
+ .expiresAt(LocalDateTime.now().plusDays(7))
.build()
);
notificationRepository.saveAll(notifications);
- log.info("Seeded {} notifications", notifications.size());
+ log.info("Seeded {} notifications for users: {}, {}, {}",
+ notifications.size(), CUSTOMER_1_ID, CUSTOMER_2_ID, EMPLOYEE_1_ID);
}
private void seedSubscriptions() {
- log.info("Seeding subscriptions...");
-
- String testUserId1 = UUID.randomUUID().toString();
- String testUserId2 = UUID.randomUUID().toString();
+ log.info("Seeding subscriptions for consistent test users...");
+ // Use consistent user IDs and predictable tokens for testing
+ // No more random UUIDs - ensures reproducible test data
List subscriptions = Arrays.asList(
Subscription.builder()
- .userId(testUserId1)
- .token("web_push_token_" + UUID.randomUUID())
+ .userId(CUSTOMER_1_ID)
+ .token("web_push_token_customer_browser")
.platform(Subscription.Platform.WEB)
.active(true)
.build(),
Subscription.builder()
- .userId(testUserId1)
- .token("ios_device_token_" + UUID.randomUUID())
+ .userId(CUSTOMER_1_ID)
+ .token("ios_device_token_customer_iphone")
.platform(Subscription.Platform.IOS)
.active(true)
.build(),
Subscription.builder()
- .userId(testUserId2)
- .token("android_device_token_" + UUID.randomUUID())
+ .userId(CUSTOMER_2_ID)
+ .token("android_device_token_testuser_phone")
.platform(Subscription.Platform.ANDROID)
.active(true)
+ .build(),
+
+ Subscription.builder()
+ .userId(EMPLOYEE_1_ID)
+ .token("web_push_token_employee_browser")
+ .platform(Subscription.Platform.WEB)
+ .active(true)
.build()
);
subscriptionRepository.saveAll(subscriptions);
- log.info("Seeded {} subscriptions", subscriptions.size());
+ log.info("Seeded {} subscriptions for users: {}, {}, {}",
+ subscriptions.size(), CUSTOMER_1_ID, CUSTOMER_2_ID, EMPLOYEE_1_ID);
}
}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/service/TransactionalEmailService.java b/notification-service/src/main/java/com/techtorque/notification_service/service/TransactionalEmailService.java
new file mode 100644
index 0000000..4931d48
--- /dev/null
+++ b/notification-service/src/main/java/com/techtorque/notification_service/service/TransactionalEmailService.java
@@ -0,0 +1,149 @@
+package com.techtorque.notification_service.service;
+
+import com.techtorque.notification.grpc.DeliveryStatus;
+import com.techtorque.notification.grpc.EmailType;
+import com.techtorque.notification_service.config.NotificationProperties;
+import java.time.OffsetDateTime;
+import java.util.Map;
+import java.util.UUID;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+/**
+ * Handles transactional email composition and delivery triggered via gRPC.
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class TransactionalEmailService {
+
+ private final JavaMailSender mailSender;
+ private final NotificationProperties notificationProperties;
+
+ @Value("${notification.email.enabled:true}")
+ private boolean emailEnabled;
+
+ public DeliveryResult sendTransactionalEmail(String to,
+ String username,
+ EmailType type,
+ Map variables) {
+ String fromAddress = resolveFromAddress();
+ EmailTemplate template = buildTemplate(username, type, variables);
+ String messageId = UUID.randomUUID().toString();
+
+ if (!emailEnabled) {
+ log.info("Email delivery disabled. Skipping send for {} ({})", to, type);
+ return new DeliveryResult(messageId, DeliveryStatus.DELIVERY_STATUS_ACCEPTED,
+ "Email delivery disabled by configuration");
+ }
+
+ try {
+ if (mailSender == null) {
+ log.warn("Mail sender not configured. Unable to deliver {} email to {}", type, to);
+ return new DeliveryResult(messageId, DeliveryStatus.DELIVERY_STATUS_REJECTED,
+ "Mail sender not configured");
+ }
+ SimpleMailMessage message = new SimpleMailMessage();
+ message.setFrom(fromAddress);
+ message.setTo(to);
+ message.setSubject(template.subject());
+ message.setText(template.body());
+ mailSender.send(message);
+ log.info("Transactional email {} queued to {} at {}", type, to, OffsetDateTime.now());
+ return new DeliveryResult(messageId, DeliveryStatus.DELIVERY_STATUS_ACCEPTED, "Email sent");
+ } catch (Exception ex) {
+ log.error("Failed to send {} email to {}: {}", type, to, ex.getMessage(), ex);
+ return new DeliveryResult(messageId, DeliveryStatus.DELIVERY_STATUS_REJECTED, ex.getMessage());
+ }
+ }
+
+ private String resolveFromAddress() {
+ String configured = notificationProperties.getEmail() != null
+ ? notificationProperties.getEmail().getFrom()
+ : null;
+ return StringUtils.hasText(configured) ? configured : "noreply@techtorque.com";
+ }
+
+ private EmailTemplate buildTemplate(String username, EmailType type, Map variables) {
+ String safeUsername = StringUtils.hasText(username) ? username : "there";
+ return switch (type) {
+ case EMAIL_TYPE_PASSWORD_RESET -> passwordResetTemplate(safeUsername, variables);
+ case EMAIL_TYPE_WELCOME -> welcomeTemplate(safeUsername, variables);
+ case EMAIL_TYPE_VERIFICATION, EMAIL_TYPE_UNSPECIFIED -> verificationTemplate(safeUsername, variables);
+ default -> verificationTemplate(safeUsername, variables);
+ };
+ }
+
+ private EmailTemplate verificationTemplate(String username, Map variables) {
+ String verificationUrl = variables.getOrDefault("verificationUrl", "");
+ String token = variables.getOrDefault("token", "");
+ String subject = "TechTorque - Verify Your Email Address";
+ StringBuilder body = new StringBuilder()
+ .append("Hello ").append(username).append(",\n\n")
+ .append("Thank you for registering with TechTorque!\n\n");
+
+ if (StringUtils.hasText(verificationUrl)) {
+ body.append("Please click the link below to verify your email address:\n")
+ .append(verificationUrl).append("\n\n");
+ } else if (StringUtils.hasText(token)) {
+ body.append("Use the following verification token to complete your registration:\n")
+ .append(token).append("\n\n");
+ }
+
+ body.append("This link will expire in 24 hours.\n\n")
+ .append("If you did not create an account, please ignore this email.\n\n")
+ .append("Best regards,\nTechTorque Team");
+ return new EmailTemplate(subject, body.toString());
+ }
+
+ private EmailTemplate passwordResetTemplate(String username, Map variables) {
+ String resetUrl = variables.getOrDefault("resetUrl", "");
+ String token = variables.getOrDefault("token", "");
+ String subject = "TechTorque - Password Reset Request";
+ StringBuilder body = new StringBuilder()
+ .append("Hello ").append(username).append(",\n\n")
+ .append("We received a request to reset your password.\n\n");
+
+ if (StringUtils.hasText(resetUrl)) {
+ body.append("Please click the link below to reset your password:\n")
+ .append(resetUrl).append("\n\n");
+ } else if (StringUtils.hasText(token)) {
+ body.append("Use the following password reset token to update your credentials:\n")
+ .append(token).append("\n\n");
+ }
+
+ body.append("This link will expire in 1 hour.\n\n")
+ .append("If you did not request a password reset, please ignore this email and your password will remain unchanged.\n\n")
+ .append("Best regards,\nTechTorque Team");
+ return new EmailTemplate(subject, body.toString());
+ }
+
+ private EmailTemplate welcomeTemplate(String username, Map variables) {
+ String dashboardUrl = variables.getOrDefault("dashboardUrl", "");
+ String subject = "Welcome to TechTorque!";
+ StringBuilder body = new StringBuilder()
+ .append("Hello ").append(username).append(",\n\n")
+ .append("Welcome to TechTorque! Your email has been successfully verified.\n\n")
+ .append("You can now:\n")
+ .append("- Register your vehicles\n")
+ .append("- Book service appointments\n")
+ .append("- Track service progress\n")
+ .append("- Request custom modifications\n\n");
+
+ if (StringUtils.hasText(dashboardUrl)) {
+ body.append("Visit ").append(dashboardUrl).append(" to get started.\n\n");
+ }
+
+ body.append("Best regards,\nTechTorque Team");
+ return new EmailTemplate(subject, body.toString());
+ }
+
+ public record DeliveryResult(String messageId, DeliveryStatus status, String detail) {}
+
+ private record EmailTemplate(String subject, String body) {}
+}
diff --git a/notification-service/src/main/java/com/techtorque/notification_service/service/impl/NotificationServiceImpl.java b/notification-service/src/main/java/com/techtorque/notification_service/service/impl/NotificationServiceImpl.java
index a0778ae..aba8631 100644
--- a/notification-service/src/main/java/com/techtorque/notification_service/service/impl/NotificationServiceImpl.java
+++ b/notification-service/src/main/java/com/techtorque/notification_service/service/impl/NotificationServiceImpl.java
@@ -1,11 +1,12 @@
package com.techtorque.notification_service.service.impl;
+import com.techtorque.notification_service.controller.WebSocketNotificationController;
import com.techtorque.notification_service.dto.response.NotificationResponse;
import com.techtorque.notification_service.entity.Notification;
import com.techtorque.notification_service.repository.NotificationRepository;
import com.techtorque.notification_service.service.NotificationService;
-import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -14,11 +15,18 @@
import java.util.stream.Collectors;
@Service
-@RequiredArgsConstructor
@Slf4j
public class NotificationServiceImpl implements NotificationService {
private final NotificationRepository notificationRepository;
+ private final WebSocketNotificationController webSocketController;
+
+ // Use @Lazy to avoid circular dependency issues
+ public NotificationServiceImpl(NotificationRepository notificationRepository,
+ @Lazy WebSocketNotificationController webSocketController) {
+ this.notificationRepository = notificationRepository;
+ this.webSocketController = webSocketController;
+ }
@Override
@Transactional(readOnly = true)
@@ -41,22 +49,27 @@ public List getUserNotifications(String userId, Boolean un
@Transactional
public NotificationResponse markAsRead(String notificationId, String userId, Boolean read) {
log.info("Marking notification {} as read: {} for user: {}", notificationId, read, userId);
-
+
Notification notification = notificationRepository.findById(notificationId)
.orElseThrow(() -> new RuntimeException("Notification not found"));
-
+
if (!notification.getUserId().equals(userId)) {
throw new RuntimeException("Unauthorized access to notification");
}
-
+
notification.setRead(read);
if (read) {
notification.setReadAt(LocalDateTime.now());
} else {
notification.setReadAt(null);
}
-
+
Notification updated = notificationRepository.save(notification);
+
+ // Send real-time unread count update via WebSocket
+ Long unreadCount = getUnreadCount(userId);
+ webSocketController.sendUnreadCountUpdate(userId, unreadCount);
+
return convertToResponse(updated);
}
@@ -78,10 +91,10 @@ public void deleteNotification(String notificationId, String userId) {
@Override
@Transactional
- public NotificationResponse createNotification(String userId, Notification.NotificationType type,
+ public NotificationResponse createNotification(String userId, Notification.NotificationType type,
String message, String details) {
log.info("Creating notification for user: {}, type: {}", userId, type);
-
+
Notification notification = Notification.builder()
.userId(userId)
.type(type)
@@ -91,9 +104,18 @@ public NotificationResponse createNotification(String userId, Notification.Notif
.deleted(false)
.expiresAt(LocalDateTime.now().plusDays(30))
.build();
-
+
Notification saved = notificationRepository.save(notification);
- return convertToResponse(saved);
+ NotificationResponse response = convertToResponse(saved);
+
+ // Send real-time notification via WebSocket
+ webSocketController.sendNotificationToUser(userId, response);
+
+ // Send updated unread count via WebSocket
+ Long unreadCount = getUnreadCount(userId);
+ webSocketController.sendUnreadCountUpdate(userId, unreadCount);
+
+ return response;
}
@Override
diff --git a/notification-service/src/main/proto/notification/email.proto b/notification-service/src/main/proto/notification/email.proto
new file mode 100644
index 0000000..7f318cc
--- /dev/null
+++ b/notification-service/src/main/proto/notification/email.proto
@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package notification.v1;
+
+option java_multiple_files = true;
+option java_package = "com.techtorque.notification.grpc";
+option java_outer_classname = "NotificationEmailProto";
+
+enum EmailType {
+ EMAIL_TYPE_UNSPECIFIED = 0;
+ EMAIL_TYPE_VERIFICATION = 1;
+ EMAIL_TYPE_PASSWORD_RESET = 2;
+ EMAIL_TYPE_WELCOME = 3;
+}
+
+enum DeliveryStatus {
+ DELIVERY_STATUS_UNSPECIFIED = 0;
+ DELIVERY_STATUS_ACCEPTED = 1;
+ DELIVERY_STATUS_REJECTED = 2;
+}
+
+message SendEmailRequest {
+ string to = 1;
+ string username = 2;
+ EmailType type = 3;
+ map variables = 4;
+ string correlation_id = 5;
+}
+
+message SendEmailResponse {
+ DeliveryStatus status = 1;
+ string message_id = 2;
+ string detail = 3;
+}
+
+service NotificationEmailService {
+ rpc SendTransactionalEmail(SendEmailRequest) returns (SendEmailResponse);
+}
diff --git a/notification-service/src/main/resources/application.properties b/notification-service/src/main/resources/application.properties
index e9f6c60..69486dd 100644
--- a/notification-service/src/main/resources/application.properties
+++ b/notification-service/src/main/resources/application.properties
@@ -25,11 +25,16 @@ spring.mail.password=${EMAIL_PASSWORD:your-app-password}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true
+notification.email.enabled=${EMAIL_ENABLED:true}
# Notification Configuration
notification.email.from=${EMAIL_USERNAME:noreply@techtorque.com}
notification.retention.days=30
+# gRPC Configuration
+grpc.server.port=${GRPC_PORT:9090}
+grpc.server.security.enabled=false
+
# Actuator Configuration
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
diff --git a/test_email_config.sh b/test_email_config.sh
deleted file mode 100755
index 56c8630..0000000
--- a/test_email_config.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/bash
-
-# Test Notification Service Email Configuration
-# This script verifies that the mail health check is properly disabled
-
-echo "=========================================="
-echo "Testing Notification Service Configuration"
-echo "=========================================="
-echo ""
-
-cd /home/randitha/Desktop/IT/UoM/TechTorque-2025/Notification_Service/notification-service
-
-echo "✓ Checking if mail health check is disabled..."
-if grep -q "management.health.mail.enabled" src/main/resources/application.properties; then
- HEALTH_STATUS=$(grep "management.health.mail.enabled" src/main/resources/application.properties | grep -o "false\|true")
- if [ "$HEALTH_STATUS" = "false" ]; then
- echo " ✓ Mail health check is DISABLED in application.properties"
- else
- echo " ✗ Mail health check is enabled (should be disabled for dev)"
- fi
-else
- echo " ⚠ Mail health check setting not found"
-fi
-
-echo ""
-echo "✓ Checking profile configuration..."
-if [ -f "src/main/resources/application-dev.properties" ]; then
- echo " ✓ Development profile exists (application-dev.properties)"
-fi
-
-if [ -f "src/main/resources/application-prod.properties" ]; then
- echo " ✓ Production profile exists (application-prod.properties)"
-fi
-
-echo ""
-echo "=========================================="
-echo "Configuration Test Summary"
-echo "=========================================="
-echo "✓ Mail health check: DISABLED (development mode)"
-echo "✓ This prevents email authentication warnings"
-echo "✓ Service will start without SMTP credential errors"
-echo ""
-echo "To enable email in production:"
-echo " 1. Set EMAIL_USERNAME and EMAIL_PASSWORD env variables"
-echo " 2. Use --spring.profiles.active=prod"
-echo " 3. See EMAIL_CONFIGURATION.md for details"
-echo ""
-echo "=========================================="