馃 distributed transcription service thistle.dunkirk.sh
1/** 2 * Email service using MailChannels API 3 * Docs: https://api.mailchannels.net/tx/v1/documentation 4 */ 5 6interface EmailAddress { 7 email: string; 8 name?: string; 9} 10 11interface EmailContent { 12 type: "text/plain" | "text/html"; 13 value: string; 14} 15 16interface SendEmailOptions { 17 to: string | EmailAddress; 18 subject: string; 19 html?: string; 20 text?: string; 21 replyTo?: string; 22} 23 24/** 25 * Send an email via MailChannels 26 */ 27export async function sendEmail(options: SendEmailOptions): Promise<void> { 28 const fromEmail = process.env.SMTP_FROM_EMAIL || "noreply@thistle.app"; 29 const fromName = process.env.SMTP_FROM_NAME || "Thistle"; 30 const dkimDomain = process.env.DKIM_DOMAIN || "thistle.app"; 31 const dkimPrivateKey = process.env.DKIM_PRIVATE_KEY; 32 const mailchannelsApiKey = process.env.MAILCHANNELS_API_KEY; 33 34 if (!dkimPrivateKey) { 35 throw new Error( 36 "DKIM_PRIVATE_KEY environment variable is required for sending emails", 37 ); 38 } 39 40 if (!mailchannelsApiKey) { 41 throw new Error( 42 "MAILCHANNELS_API_KEY environment variable is required for sending emails", 43 ); 44 } 45 46 // Normalize recipient 47 const recipient = 48 typeof options.to === "string" ? { email: options.to } : options.to; 49 50 // Build content array 51 const content: EmailContent[] = []; 52 if (options.text) { 53 content.push({ type: "text/plain", value: options.text }); 54 } 55 if (options.html) { 56 content.push({ type: "text/html", value: options.html }); 57 } 58 59 if (content.length === 0) { 60 throw new Error("At least one of 'text' or 'html' must be provided"); 61 } 62 63 const payload = { 64 personalizations: [ 65 { 66 to: [recipient], 67 ...(options.replyTo && { 68 reply_to: { email: options.replyTo }, 69 }), 70 dkim_domain: dkimDomain, 71 dkim_selector: "mailchannels", 72 dkim_private_key: dkimPrivateKey, 73 }, 74 ], 75 from: { 76 email: fromEmail, 77 name: fromName, 78 }, 79 subject: options.subject, 80 content, 81 }; 82 83 const response = await fetch("https://api.mailchannels.net/tx/v1/send", { 84 method: "POST", 85 headers: { 86 "content-type": "application/json", 87 "X-Api-Key": mailchannelsApiKey, 88 }, 89 body: JSON.stringify(payload), 90 }); 91 92 if (!response.ok) { 93 const errorText = await response.text(); 94 throw new Error( 95 `MailChannels API error (${response.status}): ${errorText}`, 96 ); 97 } 98 99 console.log(`[Email] Sent "${options.subject}" to ${recipient.email}`); 100}