fix : test email
This commit is contained in:
@@ -0,0 +1,113 @@
|
|||||||
|
# Email Blocking Diagnostic Tool
|
||||||
|
|
||||||
|
This document explains how to diagnose and fix email blocking issues in the d-mail system.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A client (`r.sadat@humapek.co`) cannot receive emails from `russel.s@metixco.ae`, but can receive emails from other senders.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
A diagnostic endpoint has been added to check if a sender domain is blocked and optionally unblock it.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Check if a domain is blocked (Diagnostic Mode)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GET /email/diagnose-blocking?recipient=r.sadat@humapek.co&senderDomain=metixco.ae
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recipientEmail": "r.sadat@humapek.co",
|
||||||
|
"senderDomain": "metixco.ae",
|
||||||
|
"user": {
|
||||||
|
"id": "user-id",
|
||||||
|
"email": "r.sadat@humapek.co",
|
||||||
|
"wildduckUserId": "wildduck-user-id",
|
||||||
|
"businessId": "business-id"
|
||||||
|
},
|
||||||
|
"domainTag": "business-id",
|
||||||
|
"blocked": true,
|
||||||
|
"blockedDomains": [
|
||||||
|
{
|
||||||
|
"domain": "metixco.ae",
|
||||||
|
"description": "Auto-blocked due to spam report",
|
||||||
|
"created": "2024-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"blockingFilters": [],
|
||||||
|
"message": "Domain metixco.ae is blocked. Use unblock=true to unblock it."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unblock a domain
|
||||||
|
|
||||||
|
```bash
|
||||||
|
GET /email/diagnose-blocking?recipient=r.sadat@humapek.co&senderDomain=metixco.ae&unblock=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recipientEmail": "r.sadat@humapek.co",
|
||||||
|
"senderDomain": "metixco.ae",
|
||||||
|
"user": { ... },
|
||||||
|
"domainTag": "business-id",
|
||||||
|
"blocked": false,
|
||||||
|
"unblocked": true,
|
||||||
|
"message": "Domain metixco.ae has been unblocked successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
This endpoint requires **Admin authentication**. Make sure you're authenticated as an admin user.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Finds the recipient user** by email address
|
||||||
|
2. **Determines the domain tag** (usually the business ID, or "default")
|
||||||
|
3. **Checks blocked domains** for that domain tag
|
||||||
|
4. **Checks email filters** that might be blocking emails from the sender domain
|
||||||
|
5. **Optionally unblocks** the domain if requested
|
||||||
|
|
||||||
|
## Common Causes
|
||||||
|
|
||||||
|
1. **Domain Blocking**: The sender domain was marked as spam and automatically blocked
|
||||||
|
2. **Email Filters**: User-created filters that delete or mark as spam emails from specific domains
|
||||||
|
3. **WildDuck Configuration**: Server-level blocking (less common)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the domain is not blocked but emails still aren't arriving:
|
||||||
|
|
||||||
|
1. Check the sender's email server logs
|
||||||
|
2. Check WildDuck server logs
|
||||||
|
3. Verify DNS/MX records for both domains
|
||||||
|
4. Check if there are any forwarding rules
|
||||||
|
5. Verify the recipient's email quota isn't exceeded
|
||||||
|
|
||||||
|
## Example: Fix the Current Issue
|
||||||
|
|
||||||
|
To fix the issue with `r.sadat@humapek.co` not receiving emails from `russel.s@metixco.ae`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# First, check the status
|
||||||
|
curl -X GET "http://your-api-url/email/diagnose-blocking?recipient=r.sadat@humapek.co&senderDomain=metixco.ae" \
|
||||||
|
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
|
||||||
|
|
||||||
|
# If blocked, unblock it
|
||||||
|
curl -X GET "http://your-api-url/email/diagnose-blocking?recipient=r.sadat@humapek.co&senderDomain=metixco.ae&unblock=true" \
|
||||||
|
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The domain tag is typically the business ID of the recipient user
|
||||||
|
- If a user doesn't have a business, the domain tag defaults to "default"
|
||||||
|
- Unblocking a domain affects all users in the same business (same domain tag)
|
||||||
|
- The diagnostic also checks for email filters that might be blocking emails
|
||||||
|
|
||||||
Generated
+22972
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,7 @@
|
|||||||
"openai": "^5.10.2",
|
"openai": "^5.10.2",
|
||||||
"parse-json": "^8.3.0",
|
"parse-json": "^8.3.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
|
"pnpm": "^10.24.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"slugify": "^1.6.6",
|
"slugify": "^1.6.6",
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { AttachmentIdQueryDto, MailboxIdQueryDto, MessageParamDto, QueueIdParamD
|
|||||||
import { MessageListQueryDto } from "./DTO/email-query.dto";
|
import { MessageListQueryDto } from "./DTO/email-query.dto";
|
||||||
import { EmailSuggestionsQueryDto, SearchMessagesQueryDto } from "./DTO/email-query.dto";
|
import { EmailSuggestionsQueryDto, SearchMessagesQueryDto } from "./DTO/email-query.dto";
|
||||||
import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
|
import { SendEmailDto, UpdateDraftDto } from "./DTO/send-email.dto";
|
||||||
|
import { EmailSpamService } from "./services/email-spam.service";
|
||||||
import { EmailService } from "./services/email.service";
|
import { EmailService } from "./services/email.service";
|
||||||
|
import { AdminRoute } from "../../common/decorators/admin.decorator";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
import { UserDec } from "../../common/decorators/user.decorator";
|
import { UserDec } from "../../common/decorators/user.decorator";
|
||||||
import { User } from "../users/entities/user.entity";
|
import { User } from "../users/entities/user.entity";
|
||||||
@@ -15,7 +17,10 @@ import { User } from "../users/entities/user.entity";
|
|||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Controller("email")
|
@Controller("email")
|
||||||
export class EmailController {
|
export class EmailController {
|
||||||
constructor(private readonly emailService: EmailService) {}
|
constructor(
|
||||||
|
private readonly emailService: EmailService,
|
||||||
|
private readonly emailSpamService: EmailSpamService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Post("send")
|
@Post("send")
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
@@ -313,4 +318,25 @@ export class EmailController {
|
|||||||
performBulkAction(@UserDec() user: User, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
performBulkAction(@UserDec() user: User, @Body() bulkActionDto: BulkActionDto, @Query() query: MailboxIdQueryDto) {
|
||||||
return this.emailService.performBulkAction(user.wildduckUserId, user.id, bulkActionDto, query.mailbox);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ export interface IGenerateTransactionalHeadersOptions extends Pick<IGenerateAnti
|
|||||||
category?: string;
|
category?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGenerateMarketingHeadersOptions
|
export interface IGenerateMarketingHeadersOptions extends Pick<
|
||||||
extends Pick<IGenerateAntiThreadingHeadersOptions, "businessDomain" | "businessName" | "listId" | "listUnsubscribe"> {
|
IGenerateAntiThreadingHeadersOptions,
|
||||||
|
"businessDomain" | "businessName" | "listId" | "listUnsubscribe"
|
||||||
|
> {
|
||||||
campaignId?: string;
|
campaignId?: string;
|
||||||
unsubscribeUrl: string;
|
unsubscribeUrl: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
|
import { InjectRepository } from "@mikro-orm/nestjs";
|
||||||
import { Injectable, Logger } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
// import { EntityRepository } from "@mikro-orm/postgresql";
|
||||||
import { firstValueFrom } from "rxjs";
|
import { firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { EmailMessage } from "../../../common/enums/message.enum";
|
import { EmailMessage } from "../../../common/enums/message.enum";
|
||||||
import { ListDomainAccessQueryDto } from "../../mail-server/DTO/domain-access.dto";
|
import { ListDomainAccessQueryDto } from "../../mail-server/DTO/domain-access.dto";
|
||||||
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
import { MailServerService } from "../../mail-server/services/mail-server.service";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
import { UserRepository } from "../../users/repositories/user.repository";
|
||||||
import { MessageSenderInfo, SpamActionOptions } from "../interfaces/email-spam.interface";
|
import { MessageSenderInfo, SpamActionOptions } from "../interfaces/email-spam.interface";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EmailSpamService {
|
export class EmailSpamService {
|
||||||
private readonly logger = new Logger(EmailSpamService.name);
|
private readonly logger = new Logger(EmailSpamService.name);
|
||||||
|
|
||||||
constructor(private readonly mailServerService: MailServerService) {}
|
constructor(
|
||||||
|
private readonly mailServerService: MailServerService,
|
||||||
|
@InjectRepository(User)
|
||||||
|
private readonly userRepository: UserRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
async getMessageSenderInfo(userId: string, messageId: number): Promise<MessageSenderInfo | null> {
|
async getMessageSenderInfo(userId: string, messageId: number): Promise<MessageSenderInfo | null> {
|
||||||
try {
|
try {
|
||||||
@@ -207,4 +215,84 @@ export class EmailSpamService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==============================================
|
||||||
|
/**
|
||||||
|
* Diagnostic method to check if a domain is blocked for a user
|
||||||
|
* and optionally unblock it
|
||||||
|
*/
|
||||||
|
async diagnoseEmailBlocking(recipientEmail: string, senderDomain: string, unblock: boolean = false) {
|
||||||
|
this.logger.log(`Diagnosing email blocking for recipient: ${recipientEmail}, sender domain: ${senderDomain}`);
|
||||||
|
|
||||||
|
// Find user by email
|
||||||
|
const user = await this.userRepository.findOne({ emailAddress: recipientEmail, deletedAt: null }, { populate: ["business"] });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error(`User with email ${recipientEmail} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const domainTag = user.business?.id || "default";
|
||||||
|
const wildduckUserId = user.wildduckUserId;
|
||||||
|
|
||||||
|
// Check blocked domains
|
||||||
|
const blockedDomainsResponse = await this.listBlockedDomains(domainTag);
|
||||||
|
const blockedDomains = blockedDomainsResponse?.results || [];
|
||||||
|
const isBlocked = blockedDomains.some((entry: any) => entry.domain?.toLowerCase() === senderDomain.toLowerCase());
|
||||||
|
|
||||||
|
// Check filters
|
||||||
|
let blockingFilters: any[] = [];
|
||||||
|
try {
|
||||||
|
const filtersResponse = await firstValueFrom(this.mailServerService.filters.listFilters(wildduckUserId));
|
||||||
|
const filters = filtersResponse?.results || [];
|
||||||
|
blockingFilters = filters.filter((filter: any) => {
|
||||||
|
const queryFrom = filter.query_from || filter.query?.find((q: any[]) => q[0] === "from")?.[1];
|
||||||
|
return (queryFrom && queryFrom.toLowerCase().includes(senderDomain.toLowerCase())) || filter.action_delete || filter.action_spam;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Could not check filters: ${error instanceof Error ? error.message : "Unknown error"}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: any = {
|
||||||
|
recipientEmail,
|
||||||
|
senderDomain,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
email: user.emailAddress,
|
||||||
|
wildduckUserId,
|
||||||
|
businessId: user.business?.id || null,
|
||||||
|
},
|
||||||
|
domainTag,
|
||||||
|
blocked: isBlocked,
|
||||||
|
blockedDomains: blockedDomains.map((entry: any) => ({
|
||||||
|
domain: entry.domain,
|
||||||
|
description: entry.description,
|
||||||
|
created: entry.created,
|
||||||
|
})),
|
||||||
|
blockingFilters: blockingFilters.map((filter: any) => ({
|
||||||
|
id: filter.id,
|
||||||
|
name: filter.name,
|
||||||
|
queryFrom: filter.query_from,
|
||||||
|
actions: filter.action || [],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unblock if requested and domain is blocked
|
||||||
|
if (isBlocked && unblock) {
|
||||||
|
try {
|
||||||
|
await this.removeDomainFromBlocklist(senderDomain, domainTag);
|
||||||
|
result.unblocked = true;
|
||||||
|
result.message = `Domain ${senderDomain} has been unblocked successfully`;
|
||||||
|
this.logger.log(`Unblocked domain ${senderDomain} for user ${recipientEmail}`);
|
||||||
|
} catch (error) {
|
||||||
|
result.unblockError = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
this.logger.error(`Failed to unblock domain: ${result.unblockError}`);
|
||||||
|
}
|
||||||
|
} else if (isBlocked && !unblock) {
|
||||||
|
result.message = `Domain ${senderDomain} is blocked. Use unblock=true to unblock it.`;
|
||||||
|
} else {
|
||||||
|
result.message = `Domain ${senderDomain} is not blocked.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user