diff --git a/apps/web/package.json b/apps/web/package.json index f690147..e5a851b 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -36,6 +36,7 @@ "bullmq": "^5.8.2", "date-fns": "^3.6.0", "hono": "^4.2.2", + "html-to-text": "^9.0.5", "install": "^0.13.0", "ioredis": "^5.4.1", "lucide-react": "^0.359.0", @@ -63,6 +64,7 @@ "@next/eslint-plugin-next": "^14.2.2", "@prisma/extension-optimize": "^0.10.0", "@types/eslint": "^8.56.2", + "@types/html-to-text": "^9.0.4", "@types/mime-types": "^2.1.4", "@types/node": "^20.11.20", "@types/react": "^18.2.57", diff --git a/apps/web/src/server/aws/ses.ts b/apps/web/src/server/aws/ses.ts index d245e82..0f23678 100644 --- a/apps/web/src/server/aws/ses.ts +++ b/apps/web/src/server/aws/ses.ts @@ -141,14 +141,18 @@ export async function sendEmailThroughSes({ }, Body: { // Body - Text: { - Data: text, // required - Charset: "UTF-8", - }, - Html: { - Data: html, // required - Charset: "UTF-8", - }, + Text: text + ? { + Data: text, // required + Charset: "UTF-8", + } + : undefined, + Html: html + ? { + Data: html, // required + Charset: "UTF-8", + } + : undefined, }, ...(unsubUrl ? { diff --git a/apps/web/src/server/service/email-queue-service.ts b/apps/web/src/server/service/email-queue-service.ts index c90109d..7d6a793 100644 --- a/apps/web/src/server/service/email-queue-service.ts +++ b/apps/web/src/server/service/email-queue-service.ts @@ -1,6 +1,7 @@ import { Job, Queue, Worker } from "bullmq"; import { env } from "~/env"; import { EmailAttachment } from "~/types"; +import { convert as htmlToText } from "html-to-text"; import { getConfigurationSetName } from "~/utils/ses-utils"; import { db } from "../db"; import { sendEmailThroughSes, sendEmailWithAttachments } from "../aws/ses"; @@ -164,13 +165,19 @@ async function executeEmail( console.log(`[EmailQueueService]: Sending email ${email.id}`); const unsubUrl = job.data.unsubUrl; + const text = email.text + ? email.text + : email.campaignId && email.html + ? htmlToText(email.html) + : undefined; + try { const messageId = attachments.length ? await sendEmailWithAttachments({ to: email.to, from: email.from, subject: email.subject, - text: email.text ?? "", + text, html: email.html ?? undefined, region: domain?.region ?? env.AWS_DEFAULT_REGION, configurationSetName, @@ -181,7 +188,7 @@ async function executeEmail( from: email.from, subject: email.subject, replyTo: email.replyTo ?? undefined, - text: email.text ?? "", + text, html: email.html ?? undefined, region: domain?.region ?? env.AWS_DEFAULT_REGION, configurationSetName, @@ -192,7 +199,7 @@ async function executeEmail( // Delete attachments after sending the email await db.email.update({ where: { id: email.id }, - data: { sesEmailId: messageId, attachments: undefined }, + data: { sesEmailId: messageId, text, attachments: undefined }, }); } catch (error: any) { await db.emailEvent.create({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f41ba4..3bba9b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -188,6 +188,9 @@ importers: hono: specifier: ^4.2.2 version: 4.2.2 + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 install: specifier: ^0.13.0 version: 0.13.0 @@ -264,6 +267,9 @@ importers: '@types/eslint': specifier: ^8.56.2 version: 8.56.5 + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 '@types/mime-types': specifier: ^2.1.4 version: 2.1.4 @@ -6481,6 +6487,10 @@ packages: dependencies: '@types/unist': 3.0.2 + /@types/html-to-text@9.0.4: + resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} + dev: true + /@types/http-cache-semantics@4.0.4: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} dev: true @@ -8913,7 +8923,7 @@ packages: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) @@ -8971,7 +8981,7 @@ packages: enhanced-resolve: 5.16.0 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -9105,41 +9115,6 @@ packages: ignore: 5.3.1 dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): - resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - dependencies: - '@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.2) - array-includes: 3.1.7 - array.prototype.findlastindex: 1.2.4 - array.prototype.flat: 1.3.2 - array.prototype.flatmap: 1.3.2 - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.57.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) - hasown: 2.0.2 - is-core-module: 2.13.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.7 - object.groupby: 1.0.2 - object.values: 1.1.7 - semver: 6.3.1 - tsconfig-paths: 3.15.0 - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} @@ -9175,6 +9150,40 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.29.1(eslint@8.57.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.4 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.2 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint@8.57.0)(typescript@5.4.2): resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}