diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js
index a1cc3f10..4b30546d 100644
--- a/backend/src/routes/adminEvents.js
+++ b/backend/src/routes/adminEvents.js
@@ -1189,6 +1189,22 @@ router.post('/:id/publish', adminAuth, requirePermission('events.edit'), require
status: 'pending',
created_at: new Date()
});
+ } else {
+ // No inline email, but the gallery may be assigned to registered customer
+ // account(s). Notify them via the account "your galleries" email
+ // (customer_gallery_assigned, in the customer's own language) instead of
+ // the gallery_created mail, which needs an inline recipient. Best-effort.
+ try {
+ const customerAccountsService = require('../services/customerAccountsService');
+ const assigned = await customerAccountsService.getAssignmentsForEvent(parseInt(id, 10));
+ for (const c of assigned.filter((a) => a.is_active !== false && a.is_active !== 0 && a.email)) {
+ await customerAccountsService
+ .notifyCustomerOfNewAssignments(c.id, [parseInt(id, 10)])
+ .catch((err) => logger.warn('Publish: customer gallery notice failed', { customerId: c.id, error: err.message }));
+ }
+ } catch (err) {
+ logger.warn('Publish: assigned-customer notification skipped', { eventId: id, error: err.message });
+ }
}
// WhatsApp gallery_ready on publish-from-draft (#640D). The PublishGallery
diff --git a/backend/src/services/customerAccountsService.js b/backend/src/services/customerAccountsService.js
index 8b8061d1..2dd76b38 100644
--- a/backend/src/services/customerAccountsService.js
+++ b/backend/src/services/customerAccountsService.js
@@ -1456,6 +1456,7 @@ module.exports = {
setAssignmentsForEvent,
setAssignmentsForCustomer,
getAssignmentsForEvent,
+ notifyCustomerOfNewAssignments,
listEventsForCustomer,
customerHasAccessToEvent,
getPendingInvitations,
diff --git a/frontend/src/components/admin/PublishGalleryDialog.tsx b/frontend/src/components/admin/PublishGalleryDialog.tsx
index d0421602..5aa09c29 100644
--- a/frontend/src/components/admin/PublishGalleryDialog.tsx
+++ b/frontend/src/components/admin/PublishGalleryDialog.tsx
@@ -7,6 +7,8 @@ interface PublishGalleryDialogProps {
eventName: string;
requirePassword: boolean;
customerEmail?: string | null;
+ /** Assigned customer accounts — notified via the account "your galleries" email when there's no inline email. */
+ assignedCustomerCount?: number;
isPublishing: boolean;
onConfirm: (password?: string) => void;
onClose: () => void;
@@ -28,11 +30,15 @@ export const PublishGalleryDialog: React.FC = ({
eventName,
requirePassword,
customerEmail,
+ assignedCustomerCount = 0,
isPublishing,
onConfirm,
onClose,
}) => {
const { t } = useTranslation();
+ // Someone gets notified if there's an inline email OR an assigned account
+ // (the latter via the account "your galleries" email).
+ const willNotify = !!customerEmail || assignedCustomerCount > 0;
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [error, setError] = useState(undefined);
@@ -72,11 +78,18 @@ export const PublishGalleryDialog: React.FC = ({
defaultValue:
'Publishing "{{eventName}}" makes the gallery accessible and sends the notification email to {{customerEmail}}.',
})
- : t('events.publishDialog.descriptionNoEmail', {
- eventName,
- defaultValue:
- 'Publishing "{{eventName}}" makes the gallery accessible. No customer email is set, so no notification will be sent.',
- })}
+ : assignedCustomerCount > 0
+ ? t('events.publishDialog.descriptionAssignedAccount', {
+ eventName,
+ count: assignedCustomerCount,
+ defaultValue:
+ 'Publishing "{{eventName}}" makes the gallery accessible. The assigned customer account(s) will be notified by email (in their language) that it is available.',
+ })
+ : t('events.publishDialog.descriptionNoEmail', {
+ eventName,
+ defaultValue:
+ 'Publishing "{{eventName}}" makes the gallery accessible. No customer email is set, so no notification will be sent.',
+ })}
{requirePassword && customerEmail && (
@@ -124,10 +137,10 @@ export const PublishGalleryDialog: React.FC = ({
onClick={handleSubmit}
disabled={isPublishing}
isLoading={isPublishing}
- leftIcon={}
+ leftIcon={willNotify ? : undefined}
className="flex-1"
>
- {t('events.publishAndNotify')}
+ {willNotify ? t('events.publishAndNotify') : t('events.publishDialog.justPublish', 'Publish')}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json
index 127a834c..ee26b006 100644
--- a/frontend/src/i18n/locales/de.json
+++ b/frontend/src/i18n/locales/de.json
@@ -1125,6 +1125,8 @@
"title": "Galerie veröffentlichen",
"descriptionWithEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht und die Benachrichtigungs-E-Mail an {{customerEmail}} gesendet.",
"descriptionNoEmail": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht. Es ist keine Kunden-E-Mail hinterlegt – es wird keine Benachrichtigung gesendet.",
+ "descriptionAssignedAccount": "Die Galerie \"{{eventName}}\" wird zugänglich gemacht. Die zugewiesenen Kundenkonten werden per E-Mail (in ihrer Sprache) benachrichtigt, dass sie verfügbar ist.",
+ "justPublish": "Veröffentlichen",
"passwordLabel": "Galerie-Passwort",
"passwordPlaceholder": "Galerie-Passwort eingeben",
"passwordHelp": "Gib das bei der Erstellung gesetzte Passwort erneut ein (oder wähle ein neues). Die E-Mail enthält genau diesen Text; das Backend hasht es erneut, sodass die Galerie-Anmeldung weiterhin funktioniert.",
diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json
index 081363f1..d15f0fe2 100644
--- a/frontend/src/i18n/locales/en.json
+++ b/frontend/src/i18n/locales/en.json
@@ -670,6 +670,8 @@
"title": "Publish gallery",
"descriptionWithEmail": "Publishing \"{{eventName}}\" makes the gallery accessible and sends the notification email to {{customerEmail}}.",
"descriptionNoEmail": "Publishing \"{{eventName}}\" makes the gallery accessible. No customer email is set, so no notification will be sent.",
+ "descriptionAssignedAccount": "Publishing \"{{eventName}}\" makes the gallery accessible. The assigned customer account(s) will be notified by email (in their language) that it is available.",
+ "justPublish": "Publish",
"passwordLabel": "Gallery password",
"passwordPlaceholder": "Enter the gallery password",
"passwordHelp": "Re-type the password set at creation (or pick a new one). The email includes this exact text; the backend re-hashes it so the gallery login still works.",
diff --git a/frontend/src/pages/admin/EventDetailsPage.tsx b/frontend/src/pages/admin/EventDetailsPage.tsx
index 079a8b73..2d7b1097 100644
--- a/frontend/src/pages/admin/EventDetailsPage.tsx
+++ b/frontend/src/pages/admin/EventDetailsPage.tsx
@@ -2661,6 +2661,7 @@ export const EventDetailsPage: React.FC = () => {
eventName={event.event_name}
requirePassword={isGalleryPublic(event) ? false : true}
customerEmail={event.customer_email}
+ assignedCustomerCount={((event as { customer_accounts?: Array<{ id: number }> }).customer_accounts || []).length}
isPublishing={publishMutation.isPending}
onConfirm={(password) => publishMutation.mutate(password)}
onClose={() => {