IN-APP Notification
Author(s)
- Ribhu
- Ayan
- Ashik
- Amarnath
Last Updated Date
2025-12-08 2025-12-11
SRS References
Version History
| Version | Date | Changes | Author |
|---|---|---|---|
| 1.0 | 2025-12-08 | Initial draft for IN-APP Notification | Ashik |
| 1.1 | 2025-12-11 | Updated GetInAppNotification to use Cursor Based Pagination | Amarnath |
Feature Overview
Objective: This document defines the architecture, workflow, and API design for IN-APP Notifications within the MyBestDealNow platform. The feature provides real-time notification management for auction-related events, allowing users to receive, view, mark as read, and delete notifications directly within the application.
Scope:
The feature enables users to stay informed about critical auction activities such as new auctions, bid placements, bid acceptance, and outbid events. All notifications are stored in the database and delivered to users in real-time using SignalR, with support for unread notification counts and bulk operations.
Dependencies:
- SignalR for real-time notification delivery
- Database for notification persistence
- IAM service for user authentication
- Auction and Bid management services for event triggers
Requirements
- System must store notifications for specific auction-related events.
- Each notification must contain comprehensive information including title, description, category, and related metadata.
- Notifications must be linked to specific users via
UserId. - System must track read/unread status of notifications.
- System must support real-time notification delivery via SignalR.
- Users must be able to retrieve their notifications with unread count.
- Users must be able to mark notifications as read (single or all).
- Users must be able to delete their notifications.
- Notifications must include optional image URLs for visual context.
- Notifications may include deep links (URL) to relevant app sections.
- System must automatically create notifications for predefined auction events.
Design Specifications
UI/UX Design:
- Display notification bell icon with unread count badge in the header.
- Show notification list in a dropdown or dedicated notifications page.
- Display notification with icon, title, description, and timestamp.
- Visual differentiation between read and unread notifications.
- Provide options to mark as read (single/all) and delete notifications.
- Support clicking notification to navigate to relevant section (via URL).
- Show loading states during real-time updates.
- Display empty state when no notifications exist.
Data Models:
Notification Model
public record Notification
{
public Guid NotificationId { get; set; }
public Guid UserId { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public InAppNotificationCategory Category { get; set; }
public bool IsRead { get; set; } = false;
public string? URL { get; set; }
public string? Image { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
public enum InAppNotificationCategory
{
All = 0,
Bids = 1,
Messages = 2,
Auctions = 3,
Systems = 4
}
public record CursorPaginationFilter
{
// Use one of these: NextCursor to get items AFTER the given cursor,
// or PreviousCursor to get items BEFORE the given cursor.
public string? NextCursor { get; set; } // fetch next page (older/newer depending on sort)
public string? PreviousCursor { get; set; } // fetch previous page
// Page size
public int Limit { get; set; } = 20;
}
public record InAppNotificationFilter : CursorPaginationFilter
{
public InAppNotificationCategory? Category { get; init; }
public bool? IsRead { get; init; }
}
public record MarkAsReadRequest
{
public Guid? NotificationId { get; set; }
public bool MarkAll { get; set; } = false;
}
public record DeleteNotificationRequest
{
public Guid? NotificationId { get; set; }
public bool DeleteAll { get; set; } = false;
}
public record AddInAppNotificationRequest
{
public Guid UserId { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public InAppNotificationCategory Category { get; set; }
public bool IsRead { get; set; } = false;
public string? URL { get; set; }
public string? Image { get; set; }
}
public class CursorPaginatedData<T>
{
public List<T> Data { get; set; } = [];
public string? NextCursor { get; set; }
public string? PreviousCursor { get; set; }
public bool HasNextPage { get; set; }
public bool HasPreviousPage { get; set; }
public int? TotalCount { get; set; }
public int Limit { get; set; }
}
public class GetNotificationsResponse : CursorPaginatedData<Notification>
{
public int UnreadCount { get; set; }
}
public record CommonResponse
{
public int Status { get; init; }
public string? Message { get; init; }
}
/// <summary>
/// MassTransit request contract for broadcasting in-app notifications via SignalR
/// This request is published by NotificationService after saving an in-app notification
/// and consumed by AuctionEngineService to broadcast via SignalR to connected clients
/// </summary>
public record BroadcastInAppNotificationRequest
{
public Guid NotificationId { get; set; }
public Guid UserId { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public InAppNotificationCategory Category { get; set; }
public bool IsRead { get; set; } = false;
public string? URL { get; set; }
public string? Image { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
API Endpoints
| Endpoint | Method | Route | Parameters / Payload | Response | Status Codes |
|---|---|---|---|---|---|
| Get Notifications | GET | /in-app | Query params (pagination, filtering) + JWT token | GetNotificationsResponse | 200, 400, 401, 500 |
| Mark Notification(s) as Read | PUT | /in-app/mark-as-read | MarkAsReadRequest (body) | CommonResponse | 200, 400, 401, 404, 500 |
| Delete Notification(s) | DELETE | /in-app/delete | DeleteNotificationRequest (body) | CommonResponse | 200, 400, 401, 404, 500 |
API Request and Responses
Get Notifications
Endpoint: GET /in-app
Controller: InAppNotificationController.GetNotifications()
Description: Retrieves paginated in-app notifications for the authenticated user along with unread count and pagination metadata. Uses cursor-based pagination for efficient data retrieval. Works with SignalR's ReceiveNotification event for real-time updates.
Authorization: Required (JWT Bearer Token)
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit | int | No | 10 | Number of notifications per page (1-100) |
nextCursor | string | No | null | Cursor for next page (Base64 encoded) |
previousCursor | string | No | null | Cursor for previous page (Base64 encoded) |
category | InAppNotificationCategory | No | null | Filter by notification category (optional) |
isRead | bool | No | null | Filter by read status (optional) |
Example Request:
GET /in-app?limit=10&category=NotifyDealersAboutNewAuction
Authorization: Bearer {token}
Success Response (200 OK):
{
"status": 200,
"message": "Notifications retrieved successfully",
"data": {
"data": [
{
"notificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"title": "New Auction Available",
"description": "A new auction for 2020 Toyota Camry has been created",
"category": "Auctions",
"isRead": false,
"url": "/auctions/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"image": "https://example.com/images/auction-123.jpg",
"createdAt": "2025-12-08T10:30:00Z"
},
{
"notificationId": "4fb85f64-5717-4562-b3fc-2c963f66afa7",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"title": "You've Been Outbid",
"description": "Your bid on 2019 Honda Accord has been outbid",
"category": "Bids",
"isRead": true,
"url": "/auctions/4fb85f64-5717-4562-b3fc-2c963f66afa7",
"image": "https://example.com/images/auction-456.jpg",
"createdAt": "2025-12-08T09:15:00Z"
}
],
"unreadCount": 15,
"nextCursor": "MjAyNS0xMi0wOFQxMDozMDowMFolMzZhODVmNjQtNTcxNy00NTYyLWIzZmMtMmM5NjNmNjZhZmE3",
"previousCursor": null,
"hasNextPage": true,
"hasPreviousPage": false,
"totalCount": 45,
"limit": 10
}
}
Response Model Fields:
| Field | Type | Description |
|---|---|---|
data | Notification[] | Array of notification objects |
unreadCount | int | Total count of unread notifications for the user |
nextCursor | string? | Cursor for fetching next page (null if no more pages) |
previousCursor | string? | Cursor for fetching previous page (null if at start) |
hasNextPage | bool | Indicates if more notifications exist |
hasPreviousPage | bool | Indicates if previous page exists |
totalCount | int | Total notifications count for user |
limit | int | Current page size limit |
Error Response (400 Bad Request):
{
"status": 400,
"message": "Invalid filter parameters"
}
Note: Returns HTTP 400 status code with error message: "Limit must be between 1 and 100", "Cannot provide both PreviousCursor and NextCursor in the same request", or "Invalid cursor format"
Error Response (401 Unauthorized):
{
"status": 401,
"message": "Invalid user token"
}
Note: Returns HTTP 401 status code when JWT token is missing, invalid, or expired.
Error Response (500 Internal Server Error):
{
"status": 500,
"message": "An internal error occurred while retrieving notifications"
}
Note: Returns HTTP 500 status code with error message describing the database or server error.
Mark Notification as Read
Endpoint: PUT /in-app/mark-as-read
Controller: InAppNotificationController.MarkAsRead()
Description: Marks one or all notifications as read for the authenticated user. The operation is validated at the controller level to ensure the notification belongs to the authenticated user.
Authorization: Required (JWT Bearer Token)
Request Body (Single Notification):
{
"notificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"markAll": false
}
Request Body (All Notifications):
{
"notificationId": null,
"markAll": true
}
Success Response (200 OK - Single):
{
"status": 200,
"message": "Notification(s) marked as read successfully"
}
Success Response (200 OK - All):
{
"status": 200,
"message": "Notification(s) marked as read successfully"
}
Error Response (400 Bad Request):
{
"status": 400,
"message": "NotificationId is required"
}
Note: Returns HTTP 400 when neither NotificationId is provided nor MarkAll is set to true.
Error Response (401 Unauthorized):
{
"status": 401,
"message": "Invalid user token"
}
Error Response (404 Not Found):
{
"status": 404,
"message": "Notification not found"
}
Error Response (500 Internal Server Error):
{
"status": 500,
"message": "An internal error occurred while marking notification as read"
}
Delete Notification
Endpoint: DELETE /in-app/delete
Controller: InAppNotificationController.DeleteNotification()
Description: Deletes one or all notifications for the authenticated user. The operation is validated at the controller level to ensure the notification belongs to the authenticated user.
Authorization: Required (JWT Bearer Token)
Request Body (Single Notification):
{
"notificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"deleteAll": false
}
Request Body (All Notifications):
{
"notificationId": null,
"deleteAll": true
}
Success Response (200 OK - Single):
{
"status": 200,
"message": "Notification(s) deleted successfully"
}
Success Response (200 OK - All):
{
"status": 200,
"message": "Notification(s) deleted successfully"
}
Error Response (400 Bad Request):
{
"status": 400,
"message": "NotificationId is required"
}
Note: Returns HTTP 400 when neither NotificationId is provided nor DeleteAll is set to true.
Error Response (401 Unauthorized):
{
"status": 401,
"message": "Invalid user token"
}
Error Response (404 Not Found):
{
"status": 404,
"message": "Notification not found"
}
Error Response (500 Internal Server Error):
{
"status": 500,
"message": "An internal error occurred while deleting notification"
}
Third-Party Integrations:
- SignalR → Real-time notification delivery via
ReceiveNotificationevent.
Workflow:
1. Notification Creation
- System detects auction-related event (new auction, bid placed, etc.)
- System creates notification record via
AddNewInAppNotification() - System determines target user(s) based on event context
- Notification stored in PostgreSQL with UTC timestamp
2. Real-Time Delivery
- System sends notification to user via SignalR
ReceiveNotificationevent - Connected clients receive notification instantly
- Client updates UI to show new notification badge
- Client increments unread count display
3. Notification Retrieval
- User opens notification panel
- Client calls
GET /in-app?limit=10 - Controller extracts userId from JWT token
- Helper validates pagination parameters
- DAL queries PostgreSQL with cursor-based pagination
- System calculates unread count
- System returns paginated notifications with cursors
- Client displays notifications with read/unread indicators
- Client displays pagination controls using cursors
4. Mark as Read (Single)
- User clicks on a notification
- Client calls
PATCH /in-app/mark-as-readwith notificationId - Controller validates JWT token and request
- DAL updates isread = true for notification
- Client updates UI to mark notification as read
- Client decrements unread count display
5. Mark as Read (All)
- User clicks "Mark all as read"
- Client calls
PATCH /in-app/mark-as-readwith markAll: true - Controller validates JWT token and request
- DAL updates all unread notifications for user
- Client clears all read indicators
- Client sets unread count to 0
6. Delete Notification (Single)
- User clicks delete icon on notification
- Client calls
DELETE /in-app/deletewith notificationId - Controller validates JWT token and request
- DAL removes notification from database
- Client removes notification from UI
7. Delete Notification (All)
- User clicks "Delete all"
- Client calls
DELETE /in-app/deletewith deleteAll: true - Controller validates JWT token and request
- DAL deletes all notifications for user
- Client clears notification list
8. Navigation
- User clicks on notification with URL
- Client extracts URL from notification object
- Client navigates to specified URL (e.g.,
/auctions/{auctionId}) - Client marks notification as read
Notification Categories & Trigger Events
1. Notify Dealers About New Auction
Trigger: When a new auction is created by a customer
Target Audience: All eligible dealers
Notification Details:
- Title: "New Auction Available"
- Description: "A new auction for [Vehicle Make Model Year] has been created"
- Category:
Auctions - URL:
/auctions/{auctionId} - Image: Vehicle primary image URL
2. Bid Place
Trigger: When any bid is placed on an auction
Target Audience: Auction owner (customer)
Notification Details:
- Title: "New Bid Received"
- Description: "[Dealer Name] placed a bid of $[Amount] on your auction"
- Category:
Bids - URL:
/auctions/{auctionId}/bids - Image: Vehicle primary image URL
3. First Bid
Trigger: When the first bid is placed on an auction
Target Audience: Auction owner (customer)
Notification Details:
- Title: "First Bid Received!"
- Description: "Congratulations! [Dealer Name] placed the first bid of $[Amount] on your auction"
- Category:
Bids - URL:
/auctions/{auctionId}/bids - Image: Vehicle primary image URL
4. New Lowest Bid Placed
Trigger: When a new lowest bid is placed on an auction
Target Audience: Auction owner (customer)
Notification Details:
- Title: "New Lowest Bid"
- Description: "[Dealer Name] placed the lowest bid of $[Amount] on your auction"
- Category:
Bids - URL:
/auctions/{auctionId}/bids - Image: Vehicle primary image URL
5. Bid Accepted
Trigger: When a dealer accepts a customer's bid
Target Audience: Bid owner (customer)
Notification Details:
- Title: "Bid Accepted!"
- Description: "Congratulations! Your bid of $[Amount] has been accepted by [Dealer Name]"
- Category:
Bids - URL:
/auctions/{auctionId} - Image: Vehicle primary image URL
6. Outbid
Trigger: When a dealer's bid is no longer the lowest bid
Target Audience: Previously lowest bidder (dealer)
Notification Details:
- Title: "You've Been Outbid"
- Description: "Your bid on [Vehicle Make Model Year] is no longer the lowest. Current lowest: $[Amount]"
- Category:
Bids - URL:
/auctions/{auctionId}/bids - Image: Vehicle primary image URL
SignalR Integration
Hub Connection
Clients must establish a SignalR connection to receive real-time notifications.
Connection URL: /notificationHub
Authentication: JWT Bearer token required in connection headers
Event: ReceiveNotification
Description: Broadcasted when a new notification is created for the user
Event Name: ReceiveNotification
Payload:
{
"notificationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userId": "550e8400-e29b-41d4-a716-446655440000",
"title": "New Auction Available",
"description": "A new auction for 2020 Toyota Camry has been created",
"category": "NotifyDealersAboutNewAuction",
"isRead": false,
"url": "/auctions/3fa85f64-5717-4562-b3fc-2c963f66afa6",
"image": "https://example.com/images/auction-123.jpg",
"createdAt": "2025-12-08T10:30:00Z"
}
Database Schema
Notifications Table
CREATE TABLE Notifications (
NotificationId UUID PRIMARY KEY DEFAULT gen_random_uuid(),
UserId UUID NOT NULL,
Title TEXT NOT NULL,
Description TEXT NOT NULL,
Category VARCHAR(255) NOT NULL,
IsRead BOOLEAN NOT NULL DEFAULT false,
URL TEXT NOT NULL,
Image VARCHAR(255) NOT NULL,
CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IX_Notifications_UserId_CreatedAt ON Notifications (UserId, CreatedAt DESC);
CREATE INDEX IX_Notifications_UserId_IsRead ON Notifications (UserId, IsRead);
Implementation Considerations
Performance Optimization
- Pagination: Implemented with configurable page size (default 10, max 100) to handle large notification lists efficiently
- Caching: Cache unread count for quick access and reduce database queries
- Indexing: Ensure proper database indexes on UserId, IsRead, and CreatedAt for optimal query performance
- Cleanup: Implement periodic cleanup of old read notifications (e.g., delete after 90 days)
- Query Optimization: Use efficient COUNT queries for pagination metadata without loading all records
Security
- Authorization: Always validate that users can only access their own notifications
- Rate Limiting: Implement rate limiting on notification endpoints
- Input Validation: Validate all request parameters to prevent injection attacks
Scalability
- SignalR Backplane: Use Redis backplane for SignalR in multi-server deployments
- Database Partitioning: Consider partitioning notifications table by date for large datasets
- Async Processing: Create notifications asynchronously to avoid blocking main operations
Notification Deduplication
Implement logic to prevent duplicate notifications for the same event:
- Track notification fingerprint (event type + entity ID + user ID)
- Check for existing notification within time window before creating new one
Development Tasks & Estimates
| No | Task Name | Estimate (Hours) | Dependencies | Notes |
|---|---|---|---|---|
| 1 | Database schema and models | 3 | Table + C# models | |
| 2 | Repository and service layer | 6 | Task 1 | CRUD + business logic |
| 3 | API endpoints (Get, MarkAsRead, Delete) | 8 | Task 2 | All 3 APIs with pagination |
| 4 | SignalR hub and real-time events | 4 | Task 1 | Hub + ReceiveNotification |
| 5 | Notification trigger handlers | 8 | Task 2, 4 | 6 event categories |
| 6 | Testing and documentation | 6 | All | Unit/integration tests |
| Total | 35 Hours |
Testing Scenarios
Functional Testing
- Verify notification creation for each category
- Test GetNotifications returns correct paginated data and unread count
- Test pagination with different page numbers and rows per page values
- Test pagination boundary conditions (first page, last page, invalid page)
- Verify HasPreviousPage and HasNextPage flags work correctly
- Test default pagination parameters (pageNumber=1, rowsPerPage=10)
- Test marking single notification as read
- Test marking all notifications as read
- Test deleting single notification
- Test deleting all notifications
- Verify SignalR real-time delivery
- Test notification filtering by user
- Verify URL and Image fields are optional
- Test concurrent notification operations
- Verify pagination metadata accuracy (TotalPages, TotalNumber)
Security Testing
- Verify users cannot access other users' notifications
- Test API authentication requirements
- Test authorization on all endpoints
- Verify SQL injection prevention
- Test rate limiting
Performance Testing
- Load test with 1000+ notifications per user
- Test SignalR scalability with multiple concurrent users
- Verify database query performance with indexes
- Test notification creation throughput
Review & Approval
Reviewer:
Approval Date: