feat: add custom email headers (#260)

This commit is contained in:
KM Koushik
2025-09-28 21:33:45 +10:00
committed by GitHub
parent 1a00999bf0
commit 890ad72057
15 changed files with 202 additions and 30 deletions
@@ -10,6 +10,7 @@ import { DEFAULT_QUEUE_OPTIONS } from "../queue/queue-constants";
import { logger } from "../logger/log";
import { createWorkerHandler, TeamJob } from "../queue/bullmq-context";
import { LimitService } from "./limit-service";
import { sanitizeCustomHeaders } from "~/server/utils/email-headers";
// Notifications about limits are handled inside LimitService.
type QueueEmailJob = TeamJob<{
@@ -127,7 +128,13 @@ export class EmailQueueService {
}
queue.add(
emailId,
{ emailId, timestamp: Date.now(), unsubUrl, isBulk, teamId },
{
emailId,
timestamp: Date.now(),
unsubUrl,
isBulk,
teamId,
},
{ jobId: emailId, delay, ...DEFAULT_QUEUE_OPTIONS }
);
}
@@ -390,6 +397,8 @@ async function executeEmail(job: QueueEmailJob) {
return;
}
const customHeaders = email.headers ? JSON.parse(email.headers) : undefined;
const messageId = await sendRawEmail({
to: email.to,
from: email.from,
@@ -407,6 +416,7 @@ async function executeEmail(job: QueueEmailJob) {
inReplyToMessageId,
emailId: email.id,
sesTenantId: domain?.sesTenantId,
headers: customHeaders,
});
logger.info(
@@ -414,10 +424,10 @@ async function executeEmail(job: QueueEmailJob) {
`[EmailQueueService]: Email sent`
);
// Delete attachments after sending the email
// Delete attachments and headers after sending the email
await db.email.update({
where: { id: email.id },
data: { sesEmailId: messageId, text, attachments: null },
data: { sesEmailId: messageId, text, attachments: null, headers: null },
});
} catch (error: any) {
await db.emailEvent.create({
+16 -5
View File
@@ -2,10 +2,15 @@ import { EmailContent } from "~/types";
import { db } from "../db";
import { UnsendApiError } from "~/server/public-api/api-error";
import { EmailQueueService } from "./email-queue-service";
import { validateDomainFromEmail, validateApiKeyDomainAccess } from "./domain-service";
import {
validateDomainFromEmail,
validateApiKeyDomainAccess,
} from "./domain-service";
import { EmailRenderer } from "@usesend/email-editor/src/renderer";
import { logger } from "../logger/log";
import { SuppressionService } from "./suppression-service";
import { sanitizeCustomHeaders } from "~/server/utils/email-headers";
import { Prisma } from "@prisma/client";
async function checkIfValidEmail(emailId: string) {
const email = await db.email.findUnique({
@@ -66,26 +71,27 @@ export async function sendEmail(
scheduledAt,
apiKeyId,
inReplyToId,
headers,
} = emailContent;
let subject = subjectFromApiCall;
let html = htmlFromApiCall;
let domain: Awaited<ReturnType<typeof validateDomainFromEmail>>;
// If this is an API call with an API key, validate domain access
if (apiKeyId) {
const apiKey = await db.apiKey.findUnique({
where: { id: apiKeyId },
include: { domain: true },
});
if (!apiKey) {
throw new UnsendApiError({
code: "BAD_REQUEST",
message: "Invalid API key",
});
}
domain = await validateApiKeyDomainAccess(from, teamId, apiKey);
} else {
// For non-API calls (dashboard, etc.), use regular domain validation
@@ -261,6 +267,7 @@ export async function sendEmail(
latestStatus: scheduledAtDate ? "SCHEDULED" : "QUEUED",
apiId: apiKeyId,
inReplyToId,
headers: headers ? JSON.stringify(headers) : undefined,
},
});
@@ -556,6 +563,9 @@ export async function sendBulkEmails(
latestStatus: "SUPPRESSED",
apiId: apiKeyId,
inReplyToId,
headers: originalContent.headers
? JSON.stringify(originalContent.headers)
: undefined,
},
});
@@ -628,6 +638,7 @@ export async function sendBulkEmails(
bcc,
scheduledAt,
apiKeyId,
headers,
} = content;
// Find the original index for this email
@@ -691,7 +702,6 @@ export async function sendBulkEmails(
: undefined;
try {
// Create email record
const email = await db.email.create({
data: {
to: Array.isArray(to) ? to : [to],
@@ -712,6 +722,7 @@ export async function sendBulkEmails(
scheduledAt: scheduledAtDate,
latestStatus: scheduledAtDate ? "SCHEDULED" : "QUEUED",
apiId: apiKeyId,
headers: headers ? JSON.stringify(headers) : undefined,
},
});