feat(pitch-deck): English email templates, investor language preference, link-only invite mode
Build pitch-deck / build-push-deploy (push) Successful in 1m55s
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 36s
CI / test-python-voice (push) Successful in 35s
CI / test-bqas (push) Successful in 35s

- Add English email template variants (greeting, message, closing, subject, CTA copy)
- Add `preferred_lang` column to `pitch_investors` — stored per investor, deck opens in that language by default
- Invite form: DE/EN language toggle that switches email defaults and pitch language setting
- Invite form: "Send email" toggle — when off, creates investor + returns magic link without sending email (for cold outreach attachment)
- `app/page.tsx`: initializes pitch language from investor's `preferred_lang` before first render (no flash)
- Migration 007 added to `/api/admin/migrate` route for production rollout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-05-06 23:18:33 +02:00
parent e013702a02
commit 17b9006b88
12 changed files with 559 additions and 130 deletions
+14 -1
View File
@@ -4,6 +4,19 @@ export const DEFAULT_MESSAGE =
export const DEFAULT_CLOSING =
'Gerne stehen wir Ihnen für einen persönlichen Austausch oder eine vertiefende Diskussion jederzeit zur Verfügung.\n\nMit freundlichen Grüßen,\nBenjamin Bönisch & Sharang Parnerkar\nGründer — BreakPilot'
export function getDefaultGreeting(name: string | null): string {
export const DEFAULT_GREETING_EN = 'Dear Sir or Madam'
export const DEFAULT_MESSAGE_EN =
'we are delighted to grant you exclusive access to the interactive investor pitch deck of our planned venture <strong>BreakPilot</strong>.<br><br>BreakPilot addresses a core challenge facing modern software development: maintaining continuous regulatory compliance while sustaining high development velocity.<br><br>Our planned solution combines code security, automated compliance, and regulatory intelligence in an end-to-end platform. The goal is not only to surface deviations, but to actively guide organizations on how to operate their systems in maximum regulatory conformance and optimal performance — particularly in the context of AI systems and the EU AI Act.<br><br>The pitch deck provides a structured overview of the problem space, solution architecture, and our planned financing strategy.'
export const DEFAULT_CLOSING_EN =
'We would be delighted to connect for a personal exchange or a deeper discussion at any time.\n\nBest regards,\nBenjamin Bönisch & Sharang Parnerkar\nFounders — BreakPilot'
export function getDefaultGreeting(name: string | null, lang: 'de' | 'en' = 'de'): string {
if (lang === 'en') return name ? `Dear ${name}` : DEFAULT_GREETING_EN
return name ? `Sehr geehrter Herr ${name}` : DEFAULT_GREETING
}
export function getDefaults(lang: 'de' | 'en') {
return lang === 'en'
? { message: DEFAULT_MESSAGE_EN, closing: DEFAULT_CLOSING_EN }
: { message: DEFAULT_MESSAGE, closing: DEFAULT_CLOSING }
}
+29 -9
View File
@@ -3,10 +3,16 @@ import nodemailer from 'nodemailer'
import {
DEFAULT_MESSAGE,
DEFAULT_CLOSING,
DEFAULT_MESSAGE_EN,
DEFAULT_CLOSING_EN,
getDefaultGreeting,
} from '@/lib/email-templates'
export { DEFAULT_GREETING, DEFAULT_MESSAGE, DEFAULT_CLOSING, getDefaultGreeting } from '@/lib/email-templates'
export {
DEFAULT_GREETING, DEFAULT_MESSAGE, DEFAULT_CLOSING,
DEFAULT_GREETING_EN, DEFAULT_MESSAGE_EN, DEFAULT_CLOSING_EN,
getDefaultGreeting, getDefaults,
} from '@/lib/email-templates'
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
@@ -28,17 +34,31 @@ export async function sendMagicLinkEmail(
greeting?: string,
message?: string,
closing?: string,
lang: 'de' | 'en' = 'de',
): Promise<void> {
const effectiveGreeting = greeting || getDefaultGreeting(investorName)
const effectiveMessage = message || DEFAULT_MESSAGE
const effectiveClosing = closing || DEFAULT_CLOSING
const effectiveGreeting = greeting || getDefaultGreeting(investorName, lang)
const effectiveMessage = message || (lang === 'en' ? DEFAULT_MESSAGE_EN : DEFAULT_MESSAGE)
const effectiveClosing = closing || (lang === 'en' ? DEFAULT_CLOSING_EN : DEFAULT_CLOSING)
const closingHtml = effectiveClosing.replace(/\n/g, '<br>')
const ttl = process.env.MAGIC_LINK_TTL_HOURS || '72'
const subject = lang === 'en'
? 'BreakPilot ComplAI — Your Personal Pitch Deck Access'
: 'BreakPilot ComplAI — Ihr persönlicher Pitch-Deck-Zugang'
const accessLabel = lang === 'en' ? 'Your Personal Access Link' : 'Ihr persönlicher Zugang'
const accessBody = lang === 'en'
? `The following link has been generated exclusively for you, is single-use, and valid for ${ttl} hours for security reasons. It grants you access to our interactive pitch deck — including an integrated AI assistant for further questions.`
: `Der folgende Link ist individuell für Sie generiert, einmalig nutzbar und aus Sicherheitsgründen für ${ttl} Stunden gültig. Er gewährt Ihnen Zugang zu unserem interaktiven Pitch Deck — inklusive integriertem KI-Assistenten für weiterführende Fragen.`
const buttonLabel = lang === 'en' ? 'Open Pitch Deck' : 'Pitch Deck öffnen'
const fallbackLabel = lang === 'en'
? `If the button doesn't work: ${magicLinkUrl}`
: `Falls der Button nicht funktioniert: ${magicLinkUrl}`
await transporter.sendMail({
from: `"${fromName}" <${fromAddr}>`,
to,
subject: 'BreakPilot ComplAI — Ihr persönlicher Pitch-Deck-Zugang',
subject,
html: `
<!DOCTYPE html>
<html>
@@ -81,10 +101,10 @@ export async function sendMagicLinkEmail(
<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 persönlicher Zugang
${accessLabel}
</p>
<p style="margin:0;font-size:13px;color:rgba(255,255,255,0.5);line-height:1.5;">
Der folgende Link ist individuell für Sie generiert, einmalig nutzbar und aus Sicherheitsgründen für ${ttl} Stunden gültig. Er gewährt Ihnen Zugang zu unserem interaktiven Pitch Deck — inklusive integriertem KI-Assistenten für weiterführende Fragen.
${accessBody}
</p>
</div>
</td>
@@ -97,7 +117,7 @@ export async function sendMagicLinkEmail(
<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 öffnen
${buttonLabel}
</a>
</td>
</tr>
@@ -109,7 +129,7 @@ export async function sendMagicLinkEmail(
<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}
${fallbackLabel}
</p>
</td>
</tr>
+1
View File
@@ -12,6 +12,7 @@ export interface Investor {
login_count: number
created_at: string
is_showcase: boolean
preferred_lang: 'de' | 'en'
}
export function useAuth() {