fix: Enhance email processing to support campaign types and improve unsubscribe logic
This commit is contained in:
@@ -59,6 +59,7 @@ export async function createEmailWorker() {
|
|||||||
contact: true,
|
contact: true,
|
||||||
project: true,
|
project: true,
|
||||||
template: {select: {type: true}},
|
template: {select: {type: true}},
|
||||||
|
campaign: {select: {type: true}},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -112,7 +113,9 @@ export async function createEmailWorker() {
|
|||||||
contact: email.contact,
|
contact: email.contact,
|
||||||
project: email.project,
|
project: email.project,
|
||||||
includeUnsubscribe:
|
includeUnsubscribe:
|
||||||
email.sourceType !== EmailSourceType.TRANSACTIONAL && email.template?.type !== 'HEADLESS',
|
email.sourceType !== EmailSourceType.TRANSACTIONAL &&
|
||||||
|
email.template?.type !== 'HEADLESS' &&
|
||||||
|
email.campaign?.type !== 'HEADLESS',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use fromName from database if available, otherwise fall back to project name
|
// Use fromName from database if available, otherwise fall back to project name
|
||||||
|
|||||||
@@ -293,9 +293,8 @@ export class EmailService {
|
|||||||
include: {
|
include: {
|
||||||
contact: true,
|
contact: true,
|
||||||
project: true,
|
project: true,
|
||||||
template: {
|
template: {select: {type: true}},
|
||||||
select: {type: true},
|
campaign: {select: {type: true}},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -363,7 +362,9 @@ export class EmailService {
|
|||||||
contact: email.contact,
|
contact: email.contact,
|
||||||
project: email.project,
|
project: email.project,
|
||||||
includeUnsubscribe:
|
includeUnsubscribe:
|
||||||
email.sourceType !== EmailSourceType.TRANSACTIONAL && email.template?.type !== 'HEADLESS',
|
email.sourceType !== EmailSourceType.TRANSACTIONAL &&
|
||||||
|
email.template?.type !== 'HEADLESS' &&
|
||||||
|
email.campaign?.type !== 'HEADLESS',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use explicit fromName if provided, otherwise fall back to project name
|
// Use explicit fromName if provided, otherwise fall back to project name
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {beforeEach, describe, expect, it, vi, type Mock} from 'vitest';
|
import {beforeEach, describe, expect, it, vi, type Mock} from 'vitest';
|
||||||
import {EmailSourceType, EmailStatus} from '@plunk/db';
|
import {EmailSourceType, EmailStatus, TemplateType} from '@plunk/db';
|
||||||
import {ActionSchemas} from '@plunk/shared';
|
import {ActionSchemas} from '@plunk/shared';
|
||||||
import {EmailService} from '../EmailService';
|
import {EmailService} from '../EmailService';
|
||||||
import {sendRawEmail} from '../SESService';
|
import {sendRawEmail} from '../SESService';
|
||||||
@@ -216,6 +216,29 @@ describe('EmailService', () => {
|
|||||||
expect(email.error).toMatch(/unsubscribed/i);
|
expect(email.error).toMatch(/unsubscribed/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should keep CAMPAIGN sourceType when campaign type is HEADLESS (no template)', async () => {
|
||||||
|
const contact = await factories.createContact({projectId, subscribed: true});
|
||||||
|
|
||||||
|
// Campaign typed HEADLESS directly — no template involved (inline body)
|
||||||
|
const campaign = await factories.createCampaign({
|
||||||
|
projectId,
|
||||||
|
type: TemplateType.HEADLESS,
|
||||||
|
body: 'Content with <a href="https://example.com/unsubscribe">unsubscribe</a>',
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await EmailService.sendCampaignEmail({
|
||||||
|
projectId,
|
||||||
|
contactId: contact.id,
|
||||||
|
campaignId: campaign.id,
|
||||||
|
subject: 'Newsletter',
|
||||||
|
body: campaign.body,
|
||||||
|
from: '[email protected]',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(email.sourceType).toBe(EmailSourceType.CAMPAIGN);
|
||||||
|
expect(email.status).toBe(EmailStatus.PENDING);
|
||||||
|
});
|
||||||
|
|
||||||
it('should keep CAMPAIGN sourceType when campaign uses headless template', async () => {
|
it('should keep CAMPAIGN sourceType when campaign uses headless template', async () => {
|
||||||
const contact = await factories.createContact({projectId, subscribed: true});
|
const contact = await factories.createContact({projectId, subscribed: true});
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface CampaignFactoryOptions {
|
|||||||
status?: CampaignStatus;
|
status?: CampaignStatus;
|
||||||
scheduledFor?: Date | null;
|
scheduledFor?: Date | null;
|
||||||
segmentId?: string | null;
|
segmentId?: string | null;
|
||||||
|
type?: TemplateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkflowFactoryOptions {
|
export interface WorkflowFactoryOptions {
|
||||||
@@ -224,6 +225,7 @@ export class TestFactories {
|
|||||||
status: options.status || CampaignStatus.DRAFT,
|
status: options.status || CampaignStatus.DRAFT,
|
||||||
scheduledFor: options.scheduledFor,
|
scheduledFor: options.scheduledFor,
|
||||||
segmentId: options.segmentId,
|
segmentId: options.segmentId,
|
||||||
|
type: options.type || TemplateType.MARKETING,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user