feat(emailing): local unsubscribe URL + full-content log driver (#22412)
In LOG mode, emit a working http://unsubscribe.<subdomain>.localhost/emailing/unsubscribe link (from SERVER_URL + workspace subdomain) instead of empty content, and log the full text/html body + List-Unsubscribe header so the flow is inspectable locally. buildUnsubscribeUrls now takes a full base URL (field renamed httpsUrl -> webUrl). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22412?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+5
-74
@@ -5,7 +5,6 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { MessageChannelType } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { EMPTY_UNSUBSCRIBE_CONTENT } from 'src/engine/core-modules/emailing-domain/constants/empty-unsubscribe-content.constant';
|
||||
import {
|
||||
EmailingDomainDriverException,
|
||||
EmailingDomainDriverExceptionCode,
|
||||
@@ -14,21 +13,12 @@ import { EmailingDomainDriverFactory } from 'src/engine/core-modules/emailing-do
|
||||
import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-status.type';
|
||||
import { EmailingDomainTenantStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-tenant-status.type';
|
||||
import { type EmailingDomainEmailContent } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-email-content.type';
|
||||
import { type EmailingDomainSendEmailInput } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-input.type';
|
||||
import { type EmailingDomainSendEmailRequest } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-input.type';
|
||||
import { type EmailingDomainSendEmailResult } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-result.type';
|
||||
import { UnsubscribeHostnameStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/unsubscribe-hostname-status.type';
|
||||
import { EmailingDomainDriver } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-driver.type';
|
||||
import { EmailingDomainEntity } from 'src/engine/core-modules/emailing-domain/emailing-domain.entity';
|
||||
import { MessageSuppressionService } from 'src/modules/emailing/services/message-suppression.service';
|
||||
import { UnsubscribeTokenService } from 'src/engine/core-modules/emailing-domain/services/unsubscribe-token.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { type DeliverableRecipients } from 'src/engine/core-modules/emailing-domain/types/deliverable-recipients.type';
|
||||
import { type UnsubscribeContent } from 'src/engine/core-modules/emailing-domain/types/unsubscribe-content.type';
|
||||
import { buildUnsubscribeHeaders } from 'src/engine/core-modules/emailing-domain/utils/build-unsubscribe-headers.util';
|
||||
import { buildUnsubscribeHtmlFooter } from 'src/engine/core-modules/emailing-domain/utils/build-unsubscribe-html-footer.util';
|
||||
import { buildUnsubscribeTextFooter } from 'src/engine/core-modules/emailing-domain/utils/build-unsubscribe-text-footer.util';
|
||||
import { buildUnsubscribeUrls } from 'src/engine/core-modules/emailing-domain/utils/build-unsubscribe-urls.util';
|
||||
import { getDomainFromEmail } from 'src/utils/get-domain-from-email';
|
||||
import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator';
|
||||
import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository';
|
||||
@@ -41,8 +31,6 @@ export class EmailingDomainSenderService {
|
||||
private readonly emailingDomainRepository: WorkspaceScopedRepository<EmailingDomainEntity>,
|
||||
private readonly emailingDomainDriverFactory: EmailingDomainDriverFactory,
|
||||
private readonly messageSuppressionService: MessageSuppressionService,
|
||||
private readonly unsubscribeTokenService: UnsubscribeTokenService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
) {}
|
||||
@@ -65,31 +53,18 @@ export class EmailingDomainSenderService {
|
||||
emailContent,
|
||||
);
|
||||
|
||||
const unsubscribe = this.buildUnsubscribeContent(
|
||||
workspaceId,
|
||||
emailingDomain,
|
||||
recipients.to[0],
|
||||
emailContent.unsubscribeTopicId,
|
||||
);
|
||||
|
||||
const replyTo = await this.resolveReplyTo(workspaceId, emailContent);
|
||||
|
||||
const emailToSend = {
|
||||
const emailToSend: EmailingDomainSendEmailRequest = {
|
||||
...emailContent,
|
||||
workspaceId,
|
||||
domain: emailingDomain.domain,
|
||||
from: emailContent.from,
|
||||
emailingDomain,
|
||||
replyTo,
|
||||
to: recipients.to,
|
||||
cc: recipients.cc,
|
||||
bcc: recipients.bcc,
|
||||
subject: emailContent.subject,
|
||||
text: `${emailContent.text}${unsubscribe.textFooter}`,
|
||||
html: isNonEmptyString(emailContent.html)
|
||||
? `${emailContent.html}${unsubscribe.htmlFooter}`
|
||||
: emailContent.html,
|
||||
attachments: emailContent.attachments,
|
||||
headers: [...(emailContent.headers ?? []), ...unsubscribe.headers],
|
||||
} as EmailingDomainSendEmailInput;
|
||||
};
|
||||
|
||||
return this.emailingDomainDriverFactory
|
||||
.getCurrentDriver()
|
||||
@@ -230,48 +205,4 @@ export class EmailingDomainSenderService {
|
||||
unsubscribeTopicId,
|
||||
);
|
||||
}
|
||||
|
||||
private buildUnsubscribeContent(
|
||||
workspaceId: string,
|
||||
emailingDomain: EmailingDomainEntity,
|
||||
primaryRecipient: string,
|
||||
unsubscribeTopicId: string | undefined,
|
||||
): UnsubscribeContent {
|
||||
const isDemoMode =
|
||||
this.twentyConfigService.get('EMAILING_DOMAIN_DRIVER') ===
|
||||
EmailingDomainDriver.LOG;
|
||||
|
||||
if (isDemoMode) {
|
||||
return EMPTY_UNSUBSCRIBE_CONTENT;
|
||||
}
|
||||
|
||||
if (
|
||||
emailingDomain.unsubscribeHostnameStatus !==
|
||||
UnsubscribeHostnameStatus.ACTIVE ||
|
||||
!isNonEmptyString(emailingDomain.unsubscribeHostname)
|
||||
) {
|
||||
throw new EmailingDomainDriverException(
|
||||
`Cannot send email for ${emailingDomain.domain}: unsubscribe domain is not active (status: ${emailingDomain.unsubscribeHostnameStatus})`,
|
||||
EmailingDomainDriverExceptionCode.UNSUBSCRIBE_NOT_READY,
|
||||
);
|
||||
}
|
||||
|
||||
const token = this.unsubscribeTokenService.sign({
|
||||
workspaceId,
|
||||
emailAddress: primaryRecipient,
|
||||
...(isNonEmptyString(unsubscribeTopicId) ? { unsubscribeTopicId } : {}),
|
||||
});
|
||||
|
||||
const unsubscribeUrls = buildUnsubscribeUrls({
|
||||
unsubscribeHostname: emailingDomain.unsubscribeHostname,
|
||||
domain: emailingDomain.domain,
|
||||
token,
|
||||
});
|
||||
|
||||
return {
|
||||
headers: buildUnsubscribeHeaders(unsubscribeUrls),
|
||||
textFooter: buildUnsubscribeTextFooter(unsubscribeUrls.httpsUrl),
|
||||
htmlFooter: buildUnsubscribeHtmlFooter(unsubscribeUrls.httpsUrl),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user