All files / lib/communication/adapters email.adapter.ts

100% Statements 54/54
100% Branches 39/39
100% Functions 3/3
100% Lines 51/51

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161        28x 26x 23x 22x       27x         28x 28x     28x 1x             27x 27x 27x     28x 28x     28x 26x         26x     27x   7x       6x   3x         2x   1x         1x   3x         3x   1x       1x   1x       1x   1x       1x   1x       1x   1x       1x   2x       2x   2x       2x   1x       1x   1x       1x   1x         1x   1x     24x   28x               3x 3x                    
import { createEmailService, renderEmailTemplate } from "../../email";
import type { AdapterResult, CommunicationQueueMessage, EmailContent } from "../types";
 
function getProviderName(env: CloudflareBindings): string {
	if (env.MAILTRAP_API_TOKEN) return "mailtrap";
	if (env.RESEND_API_KEY) return "resend";
	if (env.MAILPIT_HOST) return "mailpit";
	return "mock";
}
 
function shouldStorePreview(env: CloudflareBindings): boolean {
	return (env.ENVIRONMENT ?? "local") !== "production";
}
 
export class EmailAdapter {
	async send(message: CommunicationQueueMessage, env: CloudflareBindings): Promise<AdapterResult> {
		const provider = getProviderName(env);
		const { recipient, content } = message;
 
		// Skip delivery for phone-signup placeholder emails (not real addresses)
		if (recipient.endsWith("@phone.interioring.com")) {
			return {
				status: "sent",
				actualRecipient: recipient,
				provider: "skipped_placeholder",
			};
		}
 
		const emailContent = content as EmailContent;
		const { template, props, recipientName } = emailContent;
		const recipientName_ = recipientName ?? "";
		let previewHtml: string | undefined;
 
		try {
			const emailService = createEmailService(env);
			let result: { id: string | null; count?: number } | undefined;
 
			if (shouldStorePreview(env)) {
				const preview = await renderEmailTemplate({
					template,
					props,
					recipientName: recipientName_,
				});
				previewHtml = preview.html;
			}
 
			switch (template) {
				case "new-inquiry":
					result = await emailService.sendNewInquiry(
						[{ email: recipient, name: recipientName_ }],
						props as Parameters<typeof emailService.sendNewInquiry>[1],
					);
					break;
				case "password-reset":
					result = await emailService.sendPasswordReset(
						recipient,
						props.resetLink as string,
						props.userName as string | undefined,
					);
					break;
				case "email-verification":
					result = await emailService.sendEmailVerification(
						recipient,
						props.verificationLink as string,
						props.userName as string | undefined,
					);
					break;
				case "welcome":
					result = await emailService.sendWelcome(
						recipient,
						props.userName as string,
						props.loginUrl as string | undefined,
					);
					break;
				case "team-invitation":
					result = await emailService.sendTeamInvitation(
						recipient,
						props as Parameters<typeof emailService.sendTeamInvitation>[1],
					);
					break;
				case "blog-approval-request":
					result = await emailService.sendBlogApprovalRequest(
						recipient,
						props as Parameters<typeof emailService.sendBlogApprovalRequest>[1],
					);
					break;
				case "blog-approval-reminder":
					result = await emailService.sendBlogApprovalReminder(
						recipient,
						props as Parameters<typeof emailService.sendBlogApprovalReminder>[1],
					);
					break;
				case "blog-published":
					result = await emailService.sendBlogPublished(
						[{ email: recipient, name: recipientName_ }],
						props as Parameters<typeof emailService.sendBlogPublished>[1],
					);
					break;
				case "email-verified":
					result = await emailService.sendEmailVerified(
						message.recipient,
						emailContent.props as Parameters<typeof emailService.sendEmailVerified>[1],
					);
					break;
				case "pro-account-status":
					result = await emailService.sendProAccountStatus(
						[{ email: message.recipient, name: emailContent.recipientName ?? "" }],
						emailContent.props as Parameters<typeof emailService.sendProAccountStatus>[1],
					);
					break;
				case "website-build-result":
					result = await emailService.sendWebsiteBuildResult(
						[{ email: message.recipient, name: emailContent.recipientName ?? "" }],
						emailContent.props as Parameters<typeof emailService.sendWebsiteBuildResult>[1],
					);
					break;
				case "reminder-due":
					result = await emailService.sendReminderDue(
						recipient,
						props as Parameters<typeof emailService.sendReminderDue>[1],
					);
					break;
				case "magic-link":
					result = await emailService.sendMagicLink(
						recipient,
						props as { magicLinkUrl: string },
					);
					break;
				case "email-otp":
					result = await emailService.sendOtpCode(
						recipient,
						props.code as string,
						props.userName as string | undefined,
					);
					break;
				default:
					throw new Error(`Unknown email template: ${template}`);
			}
 
			const externalId = result?.id ?? undefined;
 
			return {
				status: "sent",
				actualRecipient: recipient,
				provider,
				externalId: externalId ?? undefined,
				previewHtml,
			};
		} catch (error) {
			const errorMessage = error instanceof Error ? error.message : String(error);
			return {
				status: "failed",
				actualRecipient: recipient,
				provider,
				previewHtml,
				errorMessage,
			};
		}
	}
}