342 lines
18 KiB
TypeScript
342 lines
18 KiB
TypeScript
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Res } from "@nestjs/common";
|
|
import { ApiOperation, ApiResponse } from "@nestjs/swagger";
|
|
import { FastifyReply } from "fastify";
|
|
|
|
import { BulkActionDto } from "./DTO/bulk-actions.dto";
|
|
import { AttachmentIdQueryDto, MailboxIdQueryDto, MessageParamDto, QueueIdParamDto } from "./DTO/email-params.dto";
|
|
import { MessageListQueryDto } from "./DTO/email-query.dto";
|
|
import { EmailSuggestionsQueryDto, SearchMessagesQueryDto } from "./DTO/email-query.dto";
|
|
import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
|
|
import { EmailSpamService } from "./services/email-spam.service";
|
|
import { EmailService } from "./services/email.service";
|
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
|
import { UserDec } from "../../common/decorators/user.decorator";
|
|
import { User } from "../users/entities/user.entity";
|
|
|
|
@AuthGuards()
|
|
@Controller("email")
|
|
export class EmailController {
|
|
constructor(
|
|
private readonly emailService: EmailService,
|
|
private readonly emailSpamService: EmailSpamService,
|
|
) {}
|
|
|
|
@Post("send")
|
|
@ApiOperation({
|
|
summary: "Send an email with configurable headers based on type",
|
|
description:
|
|
"Send emails with automatic anti-threading and anti-spam headers. Supports different email types: general, transactional, marketing, anti-threading. Marketing emails require listId and unsubscribeUrl.",
|
|
})
|
|
@ApiResponse({ status: 201, description: "Email sent successfully" })
|
|
@ApiResponse({ status: 400, description: "Invalid email data or missing required fields for email type" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to send email" })
|
|
sendEmail(@UserDec("wildduckUserId") userEmailId: string, @UserDec("id") userId: string, @Body() sendEmailDto: SendEmailDto) {
|
|
return this.emailService.sendEmail(userEmailId, userId, sendEmailDto);
|
|
}
|
|
|
|
@Get("status/:queueId")
|
|
@ApiOperation({ summary: "Get email delivery status" })
|
|
@ApiResponse({ status: 200, description: "Email delivery status" })
|
|
@ApiResponse({ status: 404, description: "Email not found" })
|
|
getEmailStatus(@Param() params: QueueIdParamDto) {
|
|
return this.emailService.getEmailStatus(params.queueId);
|
|
}
|
|
|
|
@Get("messages/:messageId")
|
|
@ApiOperation({ summary: "Get a specific message" })
|
|
@ApiResponse({ status: 200, description: "Message details" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
getMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.getMessage(user.wildduckUserId, user.id, params.messageId, query.mailbox);
|
|
}
|
|
|
|
@Delete("messages/:messageId")
|
|
@ApiOperation({ summary: "Delete a message" })
|
|
@ApiResponse({ status: 200, description: "Message deleted successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
deleteMessage(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.deleteMessage(userId, params.messageId, query.mailbox);
|
|
}
|
|
|
|
@Get("messages/:messageId/attachment")
|
|
@ApiOperation({ summary: "Download a message attachment" })
|
|
@ApiResponse({ status: 200, description: "Attachment downloaded successfully" })
|
|
@ApiResponse({ status: 404, description: "Attachment not found" })
|
|
async downloadMessageAttachment(
|
|
@UserDec("wildduckUserId") userId: string,
|
|
@Param() params: MessageParamDto,
|
|
@Query() query: AttachmentIdQueryDto,
|
|
@Res({ passthrough: false }) reply: FastifyReply,
|
|
) {
|
|
this.emailService.downloadMessageAttachment(userId, params.messageId, query.mailbox, query.attachmentId, reply);
|
|
}
|
|
|
|
@Get("messages/:messageId/download")
|
|
@ApiOperation({ summary: "Download an email" })
|
|
@ApiResponse({ status: 200, description: "Email downloaded successfully" })
|
|
@ApiResponse({ status: 404, description: "Email not found" })
|
|
downloadEmail(
|
|
@UserDec("wildduckUserId") userId: string,
|
|
@Param() params: MessageParamDto,
|
|
@Query() query: MailboxIdQueryDto,
|
|
@Res({ passthrough: false }) reply: FastifyReply,
|
|
) {
|
|
return this.emailService.downloadEmail(userId, params.messageId, query.mailbox, reply);
|
|
}
|
|
|
|
@Get("search")
|
|
@ApiOperation({ summary: "Search messages across all mailboxes" })
|
|
@ApiResponse({ status: 200, description: "Search results" })
|
|
searchMessages(@UserDec("wildduckUserId") userId: string, @Query() query: SearchMessagesQueryDto) {
|
|
return this.emailService.searchMessages(userId, query);
|
|
}
|
|
|
|
@Get("suggestions")
|
|
@ApiOperation({ summary: "Get email address suggestions for composing" })
|
|
@ApiResponse({ status: 404, description: "User not found" })
|
|
getEmailSuggestions(@UserDec("wildduckUserId") userId: string, @Query() query: EmailSuggestionsQueryDto) {
|
|
return this.emailService.getEmailSuggestions(userId, query.query, query.limit);
|
|
}
|
|
|
|
@Patch("messages/:messageId/seen")
|
|
@ApiOperation({ summary: "Mark a message as seen" })
|
|
@ApiResponse({ status: 200, description: "Message marked as seen successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
markMessageAsSeen(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.markMessageAsSeen(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/unseen")
|
|
@ApiOperation({ summary: "Mark a message as unseen" })
|
|
@ApiResponse({ status: 200, description: "Message marked as unseen successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
markMessageAsUnseen(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.markMessageAsUnseen(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/mark-all-read")
|
|
@ApiOperation({ summary: "Mark all messages as read in a mailbox" })
|
|
@ApiResponse({ status: 200, description: "All messages marked as read successfully" })
|
|
@ApiResponse({ status: 206, description: "Some messages marked as read, some failed" })
|
|
@ApiResponse({ status: 404, description: "Mailbox not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to mark messages as read" })
|
|
markAllMessagesAsRead(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.markAllMessagesAsRead(user.wildduckUserId, user.id, query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/empty-trash")
|
|
@ApiOperation({ summary: "Empty trash" })
|
|
@ApiResponse({ status: 200, description: "Trash emptied successfully" })
|
|
@ApiResponse({ status: 404, description: "Trash not found" })
|
|
emptyTrash(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.emptyTrash(user.wildduckUserId, user.id, query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/empty-spam")
|
|
@ApiOperation({ summary: "Empty spam" })
|
|
@ApiResponse({ status: 200, description: "Spam emptied successfully" })
|
|
@ApiResponse({ status: 404, description: "Spam not found" })
|
|
emptySpam(@UserDec() user: User, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.emptySpam(user.wildduckUserId, user.id, query.mailbox);
|
|
}
|
|
|
|
@Get("messages/inbox")
|
|
@ApiOperation({ summary: "List messages in a inbox" })
|
|
@ApiResponse({ status: 200, description: "List of messages" })
|
|
@ApiResponse({ status: 404, description: "Mailbox not found" })
|
|
listMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.listMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/sent")
|
|
@ApiOperation({ summary: "List messages in sent mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of sent messages" })
|
|
@ApiResponse({ status: 404, description: "Sent mailbox not found" })
|
|
getSentMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getSentMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/trash")
|
|
@ApiOperation({ summary: "List messages in trash mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of trash messages" })
|
|
@ApiResponse({ status: 404, description: "Trash mailbox not found" })
|
|
getTrashMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getTrashMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/junk")
|
|
@ApiOperation({ summary: "List messages in junk mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of junk messages" })
|
|
@ApiResponse({ status: 404, description: "junk mailbox not found" })
|
|
getJunkMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getJunkMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/archive")
|
|
@ApiOperation({ summary: "List messages in archive mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of archive messages" })
|
|
@ApiResponse({ status: 404, description: "archive mailbox not found" })
|
|
getArchiveMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getArchivedMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/favorite")
|
|
@ApiOperation({ summary: "List messages in starred mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of starred messages" })
|
|
@ApiResponse({ status: 404, description: "starred mailbox not found" })
|
|
getStarredMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getStarredMessages(userId, query);
|
|
}
|
|
|
|
@Get("messages/drafts")
|
|
@ApiOperation({ summary: "List messages in drafts mailbox" })
|
|
@ApiResponse({ status: 200, description: "List of drafts messages" })
|
|
@ApiResponse({ status: 404, description: "Drafts mailbox not found" })
|
|
getDraftsMessages(@UserDec("wildduckUserId") userId: string, @Query() query: MessageListQueryDto) {
|
|
return this.emailService.getDraftsMessages(userId, query);
|
|
}
|
|
|
|
@Patch("messages/drafts/:messageId")
|
|
@ApiOperation({ summary: "Update an existing draft" })
|
|
@ApiResponse({ status: 200, description: "Draft updated successfully" })
|
|
@ApiResponse({ status: 400, description: "Invalid draft data" })
|
|
@ApiResponse({ status: 404, description: "Draft not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to update draft" })
|
|
updateDraft(@UserDec("wildduckUserId") userEmailId: string, @Param() params: MessageParamDto, @Body() updateDraftDto: UpdateDraftDto) {
|
|
return this.emailService.updateDraft(userEmailId, Number(params.messageId), updateDraftDto);
|
|
}
|
|
|
|
@Post("messages/drafts/:messageId/send")
|
|
@ApiOperation({ summary: "Send a draft message" })
|
|
@ApiResponse({ status: 200, description: "Draft sent successfully" })
|
|
@ApiResponse({ status: 404, description: "Draft not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to send draft" })
|
|
sendDraft(@UserDec() user: User, @Param() params: MessageParamDto) {
|
|
return this.emailService.sendDraft(user.wildduckUserId, user.id, Number(params.messageId));
|
|
}
|
|
|
|
@Delete("messages/drafts/:messageId")
|
|
@ApiOperation({ summary: "Delete a draft message" })
|
|
@ApiResponse({ status: 200, description: "Draft deleted successfully" })
|
|
@ApiResponse({ status: 404, description: "Draft not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to delete draft" })
|
|
deleteDraft(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto) {
|
|
return this.emailService.deleteDraft(userId, Number(params.messageId));
|
|
}
|
|
|
|
@Patch("messages/:messageId/archive")
|
|
@ApiOperation({ summary: "Move a message to archive mailbox" })
|
|
@ApiResponse({ status: 200, description: "Message moved to archive successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToArchive(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/trash")
|
|
@ApiOperation({ summary: "Move a message to trash mailbox" })
|
|
@ApiResponse({ status: 200, description: "Message moved to trash successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToTrash(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/unarchive")
|
|
@ApiOperation({ summary: "Move a message from archive back to inbox" })
|
|
@ApiResponse({ status: 200, description: "Message moved from archive to inbox successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found in archive" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToInboxFromArchive(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToInboxFromArchive(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/favorite")
|
|
@ApiOperation({ summary: "Star a message (Gmail-like behavior)" })
|
|
@ApiResponse({ status: 200, description: "Message starred successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to star message" })
|
|
starMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.starMessage(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/unfavorite")
|
|
@ApiOperation({ summary: "Unstar a message (Gmail-like behavior)" })
|
|
@ApiResponse({ status: 200, description: "Message unstarred successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to unstar message" })
|
|
unstarMessage(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.unstarMessage(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/toggle-favorite")
|
|
@ApiOperation({ summary: "Toggle star status of a message (Gmail-like behavior)" })
|
|
@ApiResponse({ status: 200, description: "Message star status toggled successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to modify message" })
|
|
toggleMessageStar(@UserDec() user: User, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.toggleMessageStar(user.wildduckUserId, user.id, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/restore")
|
|
@ApiOperation({ summary: "Move a message from trash back to inbox" })
|
|
@ApiResponse({ status: 200, description: "Message restored to inbox successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found in trash" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToInboxFromTrash(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToInboxFromTrash(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/junk")
|
|
@ApiOperation({ summary: "Move a message to junk mailbox" })
|
|
@ApiResponse({ status: 200, description: "Message moved to junk successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToJunk(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Patch("messages/:messageId/not-junk")
|
|
@ApiOperation({ summary: "Move a message from junk back to inbox (not spam)" })
|
|
@ApiResponse({ status: 200, description: "Message moved from junk to inbox successfully" })
|
|
@ApiResponse({ status: 404, description: "Message not found in junk" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to move message" })
|
|
moveMessageToInboxFromJunk(@UserDec("wildduckUserId") userId: string, @Param() params: MessageParamDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.moveMessageToInboxFromJunk(userId, Number(params.messageId), query.mailbox);
|
|
}
|
|
|
|
@Post("messages/bulk-action")
|
|
@ApiOperation({
|
|
summary: "Perform bulk actions on multiple messages",
|
|
description: "Apply actions (seen, delete, archive, unarchive, favorite, unfavorite, junk, notjunk, trash, restore) to multiple messages at once",
|
|
})
|
|
@ApiResponse({ status: 200, description: "Bulk action completed successfully" })
|
|
@ApiResponse({ status: 206, description: "Bulk action partially completed" })
|
|
@ApiResponse({ status: 400, description: "Invalid bulk action data" })
|
|
@ApiResponse({ status: 403, description: "Not authorized to perform bulk action" })
|
|
performBulkAction(@UserDec() user: User, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
|
return this.emailService.performBulkAction(user.wildduckUserId, user.id, bulkActionDto, query.mailbox);
|
|
}
|
|
|
|
@Get("diagnose-blocking")
|
|
// @AdminRoute()
|
|
@ApiOperation({
|
|
summary: "Diagnose email blocking issues (Admin only)",
|
|
description: "Check if a sender domain is blocked for a recipient and optionally unblock it",
|
|
})
|
|
@ApiResponse({ status: 200, description: "Diagnostic information" })
|
|
@ApiResponse({ status: 400, description: "Invalid parameters" })
|
|
@ApiResponse({ status: 403, description: "Not authorized (Admin only)" })
|
|
async diagnoseEmailBlocking(
|
|
@Query("recipient") recipientEmail: string,
|
|
@Query("senderDomain") senderDomain: string,
|
|
@Query("unblock") unblock?: string,
|
|
) {
|
|
if (!recipientEmail || !senderDomain) {
|
|
throw new Error("Both recipient and senderDomain query parameters are required");
|
|
}
|
|
const shouldUnblock = unblock === "true" || unblock === "1";
|
|
return this.emailSpamService.diagnoseEmailBlocking(recipientEmail, senderDomain, shouldUnblock);
|
|
}
|
|
}
|