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:
@@ -3439,7 +3439,8 @@
|
||||
"cancel": "Abbrechen",
|
||||
"retry": "Wiederholen",
|
||||
"previewTitle": "E-Mail-Vorschau",
|
||||
"noPreview": "Keine gespeicherte Vorschau für diese E-Mail — sie wurde gesendet, bevor Vorschauen erfasst wurden."
|
||||
"noPreview": "Keine gespeicherte Vorschau für diese E-Mail — sie wurde gesendet, bevor Vorschauen erfasst wurden.",
|
||||
"reRendered": "Neu gerendert aus der aktuellen Vorlage — diese E-Mail wurde gesendet, bevor Vorschauen erfasst wurden, und kann daher leicht von der tatsächlich versendeten abweichen."
|
||||
},
|
||||
"toast": {
|
||||
"created": "Projekt erstellt",
|
||||
|
||||
@@ -3439,7 +3439,8 @@
|
||||
"cancel": "Cancel",
|
||||
"retry": "Retry",
|
||||
"previewTitle": "Email preview",
|
||||
"noPreview": "No stored preview for this email — it was sent before previews were captured."
|
||||
"noPreview": "No stored preview for this email — it was sent before previews were captured.",
|
||||
"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."
|
||||
},
|
||||
"toast": {
|
||||
"created": "Project created",
|
||||
|
||||
@@ -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.')}
|
||||
|
||||
@@ -118,6 +118,9 @@ export interface EmailPreview {
|
||||
type: string;
|
||||
status: string;
|
||||
available: boolean;
|
||||
/** true = exact bytes stored at send time; false = re-rendered from the
|
||||
* current template (approximation for emails sent before capture). */
|
||||
exact: boolean;
|
||||
html: string | null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user