Files
accounted/extensions/general/email/lib/resend-service.ts
T
Jakob Wennberg f3ec634a46 feat: open-source under AGPL-3.0, redesign UI to grayscale palette, add uncategorize API, fix VAT account names
Add LICENSE (AGPL-3.0-or-later), CONTRIBUTING.md, SECURITY.md, DCO, and NOTICE files.
Rewrite README for open-source audience with self-hosting instructions.
Redesign color palette to grayscale chrome theme across all components.
Add transaction uncategorize API route with tests.
Fix VAT account name mismatches in migration 052.
Improve import page with SIE file support and loading skeleton.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 18:18:11 +01:00

81 lines
2.3 KiB
TypeScript

/**
* Resend Email Service Implementation
*
* Implements EmailService using the Resend API.
*/
import { Resend } from 'resend'
import { createLogger } from '@/lib/logger'
import type { EmailService, SendEmailOptions, SendEmailResult } from '@/lib/email/service'
const log = createLogger('email')
const DEFAULT_FROM_EMAIL = process.env.RESEND_FROM_EMAIL || 'noreply@localhost'
let resendClient: Resend | null = null
function getResendClient(): Resend {
if (!resendClient) {
if (!process.env.RESEND_API_KEY) {
throw new Error('RESEND_API_KEY is not configured')
}
resendClient = new Resend(process.env.RESEND_API_KEY)
}
return resendClient
}
function isResendConfigured(): boolean {
return !!process.env.RESEND_API_KEY && !!process.env.RESEND_FROM_EMAIL && process.env.RESEND_FROM_EMAIL !== 'noreply@localhost'
}
export class ResendEmailService implements EmailService {
async sendEmail(options: SendEmailOptions): Promise<SendEmailResult> {
const { to, cc, subject, html, text, replyTo, fromName, attachments } = options
if (!this.isConfigured()) {
return { success: false, error: 'Email service is not configured' }
}
const from = fromName
? `${fromName} via Gnubok <${DEFAULT_FROM_EMAIL}>`
: `Gnubok <${DEFAULT_FROM_EMAIL}>`
try {
const resend = getResendClient()
const response = await resend.emails.send({
from,
to: Array.isArray(to) ? to : [to],
cc: cc ? (Array.isArray(cc) ? cc : [cc]) : undefined,
subject,
html,
text,
replyTo,
attachments: attachments?.map(att => ({
filename: att.filename,
content: typeof att.content === 'string'
? Buffer.from(att.content, 'base64')
: Buffer.from(att.content),
contentType: att.contentType,
})),
})
if (response.error) {
log.error('Resend error:', response.error)
return { success: false, error: response.error.message }
}
return { success: true, messageId: response.data?.id }
} catch (error) {
log.error('Failed to send email:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
}
}
}
isConfigured(): boolean {
return isResendConfigured()
}
}