Files
accounted/lib/email/resend.ts
T
Jakob WennbergandClaude Opus 4.6 acb85edf4a feat: wire Zod validation into API routes, improve types and components
- Add 8 new Zod schemas (UpdateCustomer, UpdateSupplier, UpdateSupplierInvoice,
  UpdateAccount, BankUnlink, RunReconciliation, CorrectJournalEntry,
  EvaluateMappingRules) and wire validateBody() into 24 JSON-body API routes
- Remove redundant manual validation checks replaced by Zod
- Add comprehensive schema tests (222 tests)
- Improve type definitions in types/index.ts with expanded interfaces
- Refactor extension types (push-notifications, receipt-ocr) for cleaner imports
- Update transaction components (BatchCategorySelector, SwipeCategorizationView,
  QuickReviewDialog, VatTreatmentSelect) and invoice inbox workspace
- Add invoice-inbox utilities and type decoupling tests
- Fix NE-bilaga, SRU export, and invoice PDF template type usage
- Update CLAUDE.md with expanded architecture documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 16:00:38 +01:00

118 lines
2.7 KiB
TypeScript

import { Resend } from 'resend'
// Default sender configuration
// Using a fixed From address with dynamic Reply-To
// From: "Företagsnamn via ERP Base" <your-from@example.com>
// Reply-To: user's company email from company_settings
// Set RESEND_FROM_EMAIL in .env.local to your verified Resend domain
const DEFAULT_FROM_EMAIL = process.env.RESEND_FROM_EMAIL || 'noreply@localhost'
// Lazy initialization to avoid errors at build time
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
}
export interface SendEmailOptions {
to: string | string[]
subject: string
html: string
text?: string
replyTo?: string
fromName?: string
attachments?: Array<{
filename: string
content: Buffer | string
contentType?: string
}>
}
export interface SendEmailResult {
success: boolean
messageId?: string
error?: string
}
/**
* Send an email via Resend
*
* @param options Email options including to, subject, html content
* @returns Result with success status and optional error
*/
export async function sendEmail(options: SendEmailOptions): Promise<SendEmailResult> {
const {
to,
subject,
html,
text,
replyTo,
fromName,
attachments
} = options
// Check configuration before attempting to send
if (!isResendConfigured()) {
return {
success: false,
error: 'Email service is not configured'
}
}
// Construct from address with optional name
const from = fromName
? `${fromName} via ERP Base <${DEFAULT_FROM_EMAIL}>`
: `ERP Base <${DEFAULT_FROM_EMAIL}>`
try {
const resend = getResendClient()
const response = await resend.emails.send({
from,
to: Array.isArray(to) ? to : [to],
subject,
html,
text,
replyTo,
attachments: attachments?.map(att => ({
filename: att.filename,
content: typeof att.content === 'string'
? Buffer.from(att.content, 'base64')
: att.content,
content_type: att.contentType
}))
})
if (response.error) {
console.error('Resend error:', response.error)
return {
success: false,
error: response.error.message
}
}
return {
success: true,
messageId: response.data?.id
}
} catch (error) {
console.error('Failed to send email:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
}
}
}
/**
* Check if Resend is properly configured
*/
export function isResendConfigured(): boolean {
return !!process.env.RESEND_API_KEY
}