- Start MongoDB locally (default connection used by this app):
mongodb://localhost:27017
- Create a Mailjet account and prepare:
API KeyandSecret Key- one verified sender email/domain in Mailjet
- Set required secrets (recommended over committing credentials to
appsettings.json):
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "SMTP:ApiKey" "your-mailjet-api-key"
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "SMTP:ApiSecret" "your-mailjet-secret-key"
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "SMTP:MailjetApiUrl" "https://api.mailjet.com/v3.1/send"
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "SMTP:FromEmail" "verified-sender@yourdomain.com"
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "SMTP:FromName" "Culinary Recipes"
dotnet user-secrets --project CulinaryRecipes.API/CulinaryRecipes.API.csproj set "Jwt:Key" "YourLongRandomJwtSigningKeyAtLeast32Chars"Alternative to user-secrets for keys:
MJ_APIKEY_PUBLICMJ_APIKEY_PRIVATE
- Run the API:
dotnet run --project CulinaryRecipes.API/CulinaryRecipes.API.csproj- Test email flow:
- Register account:
POST /api/Account/register(confirmation email is sent) - Forgot password:
POST /api/Account/forgotPassword(reset email is sent)
- Register account:
- Swagger/OpenAPI UI:
https://<api-host>/swagger - Messaging REST endpoints are under:
api/Messagingapi/Notifications
- Authentication:
Authorization: Bearer <jwt-token>
SignalR hub methods are not part of OpenAPI schema. They are documented below for frontend integration.
- GET
/api/Messaging/conversations - Response
200 OK
[
{
"id": "67ce0ab53ca0f69f633899a2",
"participantUserIds": ["user-1", "user-2"],
"createdAt": "2026-02-07T18:24:38.000Z",
"updatedAt": "2026-02-07T18:25:04.000Z",
"lastMessagePreview": "Hey, check this video",
"lastMessageAt": "2026-02-07T18:25:04.000Z"
}
]- GET
/api/Messaging/conversations/{conversationId}/messages?skip=0&take=50 - Response
200 OK
[
{
"id": "67ce0abe3ca0f69f633899a3",
"conversationId": "67ce0ab53ca0f69f633899a2",
"senderUserId": "user-1",
"recipientUserId": "user-2",
"content": "Recipe video here",
"attachments": [
{
"type": 1,
"url": "https://cdn.example.com/video.mp4",
"title": "How to make pasta",
"thumbnailUrl": "https://cdn.example.com/video-thumb.jpg"
}
],
"sentAt": "2026-02-07T18:25:04.000Z",
"isRead": false
}
]- POST
/api/Messaging/requests - Body
{
"recipientUserId": "user-2"
}- POST
/api/Messaging/requests/{requestId}/respond - Body
{
"accept": true
}- POST
/api/Messaging/messages - Body
{
"conversationId": "67ce0ab53ca0f69f633899a2",
"recipientUserId": "user-2",
"content": "Here is the recipe photo + link",
"attachments": [
{
"type": 0,
"url": "https://cdn.example.com/photo.jpg",
"title": "Dish photo",
"thumbnailUrl": ""
},
{
"type": 2,
"url": "https://example.com/recipe",
"title": "Recipe link",
"thumbnailUrl": "https://cdn.example.com/link-thumb.jpg"
}
]
}- GET
/api/Notifications?unreadOnly=false&take=50 - GET
/api/Notifications/unread-count - POST
/api/Notifications/{notificationId}/read
0=Photo1=Video2=Link
0=Pending1=Accepted2=Rejected
0=MessageRequest1=Message2=Like3=Action
- Hub endpoint:
/hubs/messaging - Auth: same JWT (Bearer). For JS client, use
accessTokenFactory.
Handshake()SendMessageRequest(CreateMessageRequestModel model)RespondToMessageRequest(string requestId, RespondMessageRequestModel model)SendMessage(SendMessageModel model)
HandshakeAcknowledged(MessagingHandshake handshake)MessageRequestReceived(MessageRequest request)MessageRequestUpdated(MessageRequest request)MessageReceived(ChatMessage message)NotificationReceived(Notification notification)
{
"userId": "user-1",
"connectionId": "YvP0r7k6O9v8Q8R0W4eY5Q",
"serverTimeUtc": "2026-02-07T18:23:22.000Z",
"pendingRequestCount": 1,
"unreadNotificationCount": 4
}import * as signalR from "@microsoft/signalr";
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://<api-host>/hubs/messaging", {
accessTokenFactory: () => jwtToken
})
.withAutomaticReconnect()
.build();
connection.on("HandshakeAcknowledged", (handshake) => {
console.log("connected", handshake);
});
connection.on("MessageReceived", (message) => {
console.log("new message", message);
});
connection.on("NotificationReceived", (notification) => {
console.log("notification", notification);
});
await connection.start();
await connection.invoke("Handshake");