fix(projects): scope email rollup to CRM types + re-render unstored previews

- The customer address often doubles as the admin notification target, so
  matching emails purely by recipient swept in system alerts (backup_failed,
  restore_failed, …). The recipient match is now restricted to CRM document
  types (quote_/contract_/invoice_/storno_); event-scoped mails still match
  by event_id.
- getEmailPreview now falls back to renderQueuedEmail() — re-rendering from
  the current template + the row's stored email_data — for emails sent before
  rendered_html capture, flagged exact:false with an amber 're-rendered' note.
  Only a missing template / no variables falls through to 'nothing stored'.

Known limitation: a customer with multiple projects sees their event_id=null
CRM mails under each (email_queue has no project_id).
This commit is contained in:
Luca
2026-06-06 22:47:43 +02:00
parent c0b6d14d08
commit f02fba6332
6 changed files with 76 additions and 14 deletions
@@ -404,7 +404,14 @@ export const ProjectCockpitPage: React.FC = () => {
{previewLoading ? (
<Loading />
) : preview && preview.available && preview.html ? (
<iframe title="email-preview" srcDoc={preview.html} className="w-full h-[60vh] border border-neutral-200 dark:border-neutral-700 rounded bg-white" />
<>
{!preview.exact && (
<div className="mb-3 rounded-md bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-800 px-3 py-2 text-xs text-amber-800 dark:text-amber-200">
{t('projects.email.reRendered', 'Re-rendered from the current template — this email was sent before previews were captured, so it may differ slightly from what the recipient received.')}
</div>
)}
<iframe title="email-preview" srcDoc={preview.html} className="w-full h-[60vh] border border-neutral-200 dark:border-neutral-700 rounded bg-white" />
</>
) : (
<div className="text-center py-10 text-neutral-500">
{t('projects.email.noPreview', 'No stored preview for this email — it was sent before previews were captured.')}