Generate an editable norm-compliant cover email per application

Alongside the tailored PDFs, the LLM now produces a short, DIN-style
Begleit-E-Mail (subject + body) grounded in the same data: same
salutation as the cover letter, the target role, a line or two of core
motivation, a pointer to the attached documents, and a friendly close.

Persisted in new bewerbungen columns (email_betreff, email_anschreiben)
and surfaced in an editable subject field + textarea on the detail page,
so the applicant can tweak it before sending; a copy button puts subject
and body on the clipboard. Values are stored raw and HTML-escaped on
render to avoid double-escaping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 15:20:56 +02:00
co-authored by Claude Opus 4.8
parent 0bfa5683fb
commit 3c553cadf5
3 changed files with 113 additions and 5 deletions
+52
View File
@@ -180,6 +180,45 @@
<% } %>
</div>
<!-- Editable e-mail cover text (Begleit-E-Mail) -->
<% if ((application.email_anschreiben && application.email_anschreiben.trim()) || genStatus === 'fertig') { %>
<div id="email" class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-5">
<div class="flex flex-wrap items-center justify-between gap-2 mb-1">
<h3 class="text-sm font-semibold text-gray-800 dark:text-white">Begleit-E-Mail</h3>
<button type="button" id="emailCopyBtn"
class="inline-flex items-center gap-1 text-xs text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
<span id="emailCopyLabel">E-Mail-Text kopieren</span>
</button>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
Kurzer, normgerechter Text zum Versand der Bewerbung per E-Mail (Unterlagen im Anhang).
Du kannst Betreff und Text vor dem Versand frei anpassen und speichern.
</p>
<form action="/bewerbung/<%= application.id %>/email" method="POST">
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Betreff</label>
<input type="text" name="email_betreff" id="emailBetreff" value="<%= application.email_betreff || '' %>"
class="w-full px-3 py-2 mb-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
placeholder="Bewerbung als …">
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">E-Mail-Text</label>
<textarea name="email_anschreiben" id="emailText" rows="12"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed font-sans"
placeholder="Sehr geehrte Damen und Herren,&#10;&#10;…"><%= application.email_anschreiben || '' %></textarea>
<div class="flex justify-end mt-3">
<button type="submit"
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
E-Mail-Text speichern
</button>
</div>
</form>
</div>
<% } %>
<!-- LLM notes + generate/regenerate -->
<form action="/bewerbung/<%= application.id %>/generieren" method="POST"
class="mt-6 border-t border-gray-200 dark:border-gray-700 pt-5"
@@ -345,6 +384,19 @@
const cy = document.getElementById('currentYear');
if (cy) cy.textContent = new Date().getFullYear();
// Copy the e-mail cover text (subject + body) to the clipboard.
const copyBtn = document.getElementById('emailCopyBtn');
if (copyBtn) copyBtn.addEventListener('click', async () => {
const betreff = (document.getElementById('emailBetreff') || {}).value || '';
const body = (document.getElementById('emailText') || {}).value || '';
const label = document.getElementById('emailCopyLabel');
const payload = (betreff ? 'Betreff: ' + betreff + '\n\n' : '') + body;
try {
await navigator.clipboard.writeText(payload);
if (label) { const t = label.textContent; label.textContent = 'Kopiert!'; setTimeout(() => { label.textContent = t; }, 1500); }
} catch (e) { /* clipboard blocked — ignore */ }
});
// While documents are being generated, poll for completion and
// reload the page once the status changes.
const unterlagen = document.getElementById('unterlagen');