/**
* POST /api/offers/quote/send
* Body: OfferQuotePayload incl. email { to, cc?, subject, message }.
* Builds the branded Angebot PDF ONCE, emails it to the recipient
* (from info@logyou.de — quotes are sales conversations, replies land in the
* real inbox) and attaches the same PDF to the triggering record:
* partner → C_BPartner, lead → Lead_User (the lead page's AttachmentModal target).
*/
import nodemailer from 'nodemailer'
import refreshTokenHelper from '../../../utils/refreshTokenHelper'
import errorHandlingHelper from '../../../utils/errorHandlingHelper'
import forceLogoutHelper from '../../../utils/forceLogoutHelper'
import { generateQuotePdf, BRAND, LEGAL_FOOTER_LINES } from '../../../utils/offers/quotePdf'
import { attachToStrapi } from '../../../utils/inbox/strapiAttach'
const EMAIL_TEXTS: any = {
de: {
headline: 'Ihr Fulfillment-Angebot',
attachmentBox: 'Das Angebot liegt dieser E-Mail als PDF-Anhang bei.',
validity: (days: number) => `Gültigkeit: ${days} Tage.`,
auto: 'Diese E-Mail wurde automatisch versendet.'
},
en: {
headline: 'Your fulfillment quote',
attachmentBox: 'The quote is attached to this email as a PDF.',
validity: (days: number) => `Validity: ${days} days.`,
auto: 'This email was sent automatically.'
}
}
const generateQuoteEmailHtml = (message: string, language: string, validDays: number) => {
const T = EMAIL_TEXTS[language === 'en' ? 'en' : 'de']
const messageHtml = String(message || '')
.replace(/&/g, '&').replace(//g, '>')
.replace(/\n/g, '
')
return `
|
${T.headline}
LogYou GmbH · logyou.de
|
|
${messageHtml}
|
|
📎 ${T.attachmentBox} ${T.validity(validDays)}
|
|
${LEGAL_FOOTER_LINES.join(' ')}
${T.auto}
|
|
`
}
const handleFunc = async (event: any, authToken: string | null = null) => {
const token = authToken ?? await getTokenHelper(event)
if (!token) return { status: 401, message: 'Not authenticated' }
const body = await readBody(event)
if (!body?.customer?.company || !body?.customer?.contactName) {
return { status: 400, message: 'Company and contact name are required' }
}
const toEmail = String(body?.email?.to || '').trim()
if (!/.+@.+\..+/.test(toEmail)) {
return { status: 400, message: 'A valid recipient email is required' }
}
const language = body?.meta?.language === 'en' ? 'en' : 'de'
const validDays = Number(body?.meta?.validDays) || 7
// Build the PDF once — reused for the email attachment AND the record attachment.
const { buffer, filename } = await generateQuotePdf(body)
const transporter = nodemailer.createTransport({
host: 'localhost',
port: 25,
secure: false,
tls: {
rejectUnauthorized: false
}
})
const ccList = String(body?.email?.cc || '')
.split(/[,;]/)
.map((s: string) => s.trim())
.filter((s: string) => s.length > 0)
try {
await transporter.sendMail({
from: 'info@logyou.de',
to: toEmail,
cc: ccList.length > 0 ? ccList : undefined,
subject: body?.email?.subject || filename.replace(/\.pdf$/, ''),
text: body?.email?.message || '',
html: generateQuoteEmailHtml(body?.email?.message || '', language, validDays),
attachments: [
{
filename,
content: buffer,
contentType: 'application/pdf'
}
]
})
} catch (err: any) {
console.error('Quote email send error:', err)
return { status: 500, message: `Failed to send email: ${err.message}` }
}
// Attach the PDF to the triggering record for later reference — its own
// try/catch so an attachment failure never reverts the successful send.
let attached = false
let attachWarning = ''
try {
const tableName = body.source === 'lead' ? 'Lead_User' : 'C_BPartner'
const recordId = Number(body.recordId)
if (recordId > 0) {
await attachToStrapi(event, token, {
tableName,
recordId,
recordUu: body.recordUu || undefined,
buffer,
filename,
mimeType: 'application/pdf'
})
attached = true
} else {
attachWarning = 'No record id — attachment skipped'
}
} catch (err: any) {
console.error('Quote attachment error:', err)
attachWarning = 'Email sent, but attaching the PDF to the record failed'
}
return { status: 200, message: 'Quote sent successfully', filename, attached, warning: attachWarning || undefined }
}
export default defineEventHandler(async (event) => {
let data: any = {}
try {
data = await handleFunc(event)
} catch (err: any) {
try {
let authToken: any = await refreshTokenHelper(event)
data = await handleFunc(event, authToken)
} catch (error: any) {
data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
forceLogoutHelper(event, data)
}
}
return data
})