All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m40s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 45s
CI / test-python-voice (push) Successful in 31s
CI / test-bqas (push) Successful in 43s
Client component (investors/new page) imported DEFAULT_MESSAGE etc. from lib/email.ts which also top-level initialises nodemailer — webpack tried to bundle fs/net/dns into the client chunk and failed. Extract the pure constants + getDefaultGreeting into lib/email-templates.ts (client-safe), keep nodemailer in lib/email.ts (server-only), update the page to import from email-templates. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
165 lines
8.7 KiB
TypeScript
165 lines
8.7 KiB
TypeScript
import 'server-only'
|
|
import nodemailer from 'nodemailer'
|
|
import {
|
|
DEFAULT_MESSAGE,
|
|
DEFAULT_CLOSING,
|
|
getDefaultGreeting,
|
|
} from '@/lib/email-templates'
|
|
|
|
export { DEFAULT_GREETING, DEFAULT_MESSAGE, DEFAULT_CLOSING, getDefaultGreeting } from '@/lib/email-templates'
|
|
|
|
const transporter = nodemailer.createTransport({
|
|
host: process.env.SMTP_HOST,
|
|
port: parseInt(process.env.SMTP_PORT || '587'),
|
|
secure: process.env.SMTP_PORT === '465',
|
|
auth: {
|
|
user: process.env.SMTP_USERNAME,
|
|
pass: process.env.SMTP_PASSWORD,
|
|
},
|
|
})
|
|
|
|
const fromName = process.env.SMTP_FROM_NAME || 'BreakPilot'
|
|
const fromAddr = process.env.SMTP_FROM_ADDR || 'noreply@breakpilot.ai'
|
|
|
|
export async function sendMagicLinkEmail(
|
|
to: string,
|
|
investorName: string | null,
|
|
magicLinkUrl: string,
|
|
greeting?: string,
|
|
message?: string,
|
|
closing?: string,
|
|
): Promise<void> {
|
|
const effectiveGreeting = greeting || getDefaultGreeting(investorName)
|
|
const effectiveMessage = message || DEFAULT_MESSAGE
|
|
const effectiveClosing = closing || DEFAULT_CLOSING
|
|
const closingHtml = effectiveClosing.replace(/\n/g, '<br>')
|
|
const ttl = process.env.MAGIC_LINK_TTL_HOURS || '72'
|
|
|
|
await transporter.sendMail({
|
|
from: `"${fromName}" <${fromAddr}>`,
|
|
to,
|
|
subject: 'BreakPilot ComplAI — Ihr persönlicher Pitch-Deck-Zugang',
|
|
html: `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
</head>
|
|
<body style="margin:0;padding:0;background:#0a0a1a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a0a1a;padding:40px 20px;">
|
|
<tr>
|
|
<td align="center">
|
|
<table width="560" cellpadding="0" cellspacing="0" style="background:#111127;border-radius:12px;border:1px solid rgba(99,102,241,0.2);">
|
|
|
|
<!-- Header -->
|
|
<tr>
|
|
<td style="padding:40px 40px 20px;">
|
|
<h1 style="margin:0;font-size:24px;color:#e0e0ff;font-weight:600;">
|
|
BreakPilot ComplAI
|
|
</h1>
|
|
<p style="margin:8px 0 0;font-size:13px;color:rgba(255,255,255,0.4);">
|
|
Investor Pitch Deck
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Greeting + Message -->
|
|
<tr>
|
|
<td style="padding:20px 40px 0;">
|
|
<p style="margin:0 0 16px;font-size:16px;color:rgba(255,255,255,0.8);line-height:1.6;">
|
|
${effectiveGreeting},
|
|
</p>
|
|
<p style="margin:0 0 24px;font-size:15px;color:rgba(255,255,255,0.7);line-height:1.7;">
|
|
${effectiveMessage}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Magic Link Explanation -->
|
|
<tr>
|
|
<td style="padding:0 40px 20px;">
|
|
<div style="background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.15);border-radius:8px;padding:16px;">
|
|
<p style="margin:0 0 4px;font-size:11px;font-weight:600;color:rgba(99,102,241,0.8);text-transform:uppercase;letter-spacing:0.5px;">
|
|
Ihr persoenlicher Zugangslink
|
|
</p>
|
|
<p style="margin:0;font-size:13px;color:rgba(255,255,255,0.5);line-height:1.5;">
|
|
Der untenstehende Link ist einmalig und verfaellt nach ${ttl} Stunden. Er gewaehrt Ihnen exklusiven Zugang zu unserem interaktiven Pitch Deck — inklusive KI-Assistent fuer Ihre Fragen.
|
|
</p>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Button -->
|
|
<tr>
|
|
<td style="padding:0 40px 16px;" align="center">
|
|
<table cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td style="background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:8px;padding:14px 40px;">
|
|
<a href="${magicLinkUrl}" style="color:#ffffff;font-size:16px;font-weight:600;text-decoration:none;display:inline-block;">
|
|
Pitch Deck oeffnen
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Raw link -->
|
|
<tr>
|
|
<td style="padding:0 40px 24px;">
|
|
<p style="margin:0;font-size:11px;color:rgba(255,255,255,0.25);line-height:1.5;word-break:break-all;">
|
|
Falls der Button nicht funktioniert: ${magicLinkUrl}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Closing -->
|
|
<tr>
|
|
<td style="padding:0 40px 24px;">
|
|
<p style="margin:0;font-size:15px;color:rgba(255,255,255,0.7);line-height:1.7;">
|
|
${closingHtml}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Legal Footer DE -->
|
|
<tr>
|
|
<td style="padding:20px 40px 12px;border-top:1px solid rgba(255,255,255,0.05);">
|
|
<p style="margin:0 0 8px;font-size:10px;font-weight:600;color:rgba(255,255,255,0.3);text-transform:uppercase;letter-spacing:0.5px;">
|
|
Vertraulichkeit & Haftungsausschluss
|
|
</p>
|
|
<p style="margin:0 0 6px;font-size:10px;color:rgba(255,255,255,0.18);line-height:1.5;">
|
|
Dieses Pitch Deck ist vertraulich und wurde ausschliesslich fuer den namentlich eingeladenen Empfaenger erstellt. Durch das Oeffnen des Links erklaert sich der Empfaenger einverstanden: (a) Der Inhalt ist vertraulich zu behandeln und darf nicht an Dritte weitergegeben, kopiert oder zugaenglich gemacht werden. Ausgenommen sind Berater (Rechtsanwaelte, Steuerberater), die berufsrechtlich zur Verschwiegenheit verpflichtet sind. (b) Die Informationen duerfen ausschliesslich zur Bewertung einer moeglichen Beteiligung verwendet werden. (c) Diese Vertraulichkeitsverpflichtung gilt fuer drei (3) Jahre ab Uebermittlung, unabhaengig davon, ob eine Beteiligung zustande kommt.
|
|
</p>
|
|
<p style="margin:0 0 12px;font-size:10px;color:rgba(255,255,255,0.18);line-height:1.5;">
|
|
Dieses Dokument stellt weder ein Angebot zum Verkauf noch eine Aufforderung zur Abgabe eines Angebots zum Erwerb von Wertpapieren dar. Es handelt sich nicht um einen Wertpapierprospekt. Alle Finanzangaben sind Planzahlen und stellen keine Garantie fuer kuenftige Ergebnisse dar. Eine Beteiligung an einem jungen Unternehmen ist mit erheblichen Risiken verbunden, einschliesslich des Risikos eines Totalverlusts. Es gilt deutsches Recht. Gerichtsstand ist Konstanz, Deutschland.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- Legal Footer EN -->
|
|
<tr>
|
|
<td style="padding:0 40px 40px;">
|
|
<p style="margin:0 0 8px;font-size:10px;font-weight:600;color:rgba(255,255,255,0.2);text-transform:uppercase;letter-spacing:0.5px;">
|
|
Confidentiality & Disclaimer
|
|
</p>
|
|
<p style="margin:0 0 6px;font-size:10px;color:rgba(255,255,255,0.13);line-height:1.5;">
|
|
This pitch deck is confidential and has been prepared exclusively for the personally invited recipient. By opening this link, the recipient agrees: (a) The content must be treated confidentially and may not be disclosed, copied or made accessible to third parties. Excluded are advisors (lawyers, tax advisors) professionally bound to secrecy. (b) The information may only be used for evaluating a possible participation. (c) This confidentiality obligation applies for three (3) years from transmission, regardless of whether a participation materializes.
|
|
</p>
|
|
<p style="margin:0;font-size:10px;color:rgba(255,255,255,0.13);line-height:1.5;">
|
|
This document constitutes neither an offer to sell nor a solicitation of an offer to acquire securities. It is not a securities prospectus. All financial figures are projections and do not constitute a guarantee of future results. An investment in a young company involves significant risks, including the risk of total loss. German law applies. Place of jurisdiction is Konstanz, Germany.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
`,
|
|
})
|
|
}
|