From 967233be4086523266d055a25067ff4727be71aa Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 9 Sep 2026 12:34:26 +0200 Subject: [PATCH 1/2] feat(events): organizer name + reply-to fields in the event editor Pairs with aiolabs/events v1.6.1-aio.11: `extra.organizer_name` becomes the From display name of ticket emails ("Organizer via ") and `extra.reply_to_email` the Reply-To (blank = the organizer's account email). Both live under the notifications section next to the per-event subject/body; the reply-to is validated as an email or left empty. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo --- .../events/components/CreateEventDialog.vue | 45 +++++++++++++++++++ src/modules/events/types/ticket.ts | 6 +++ 2 files changed, 51 insertions(+) diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index ee8f487..bb7f20e 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -71,6 +71,10 @@ interface Props { autoApprove?: boolean } +// Instance name used in the From display name of ticket emails +// (the LNbits site title on the backend; the standalone's brand here). +const siteName = (import.meta.env.VITE_APP_NAME as string) || 'Events' + const props = defineProps() const emit = defineEmits<{ 'update:open': [value: boolean] @@ -144,6 +148,12 @@ const formSchema = toTypedSchema( nostr_notifications: z.boolean().default(false), notification_subject: z.string().max(200).default(''), notification_body: z.string().max(2000).default(''), + organizer_name: z.string().trim().max(100).default(''), + reply_to_email: z + .string() + .trim() + .default('') + .refine((v) => v === '' || z.string().email().safeParse(v).success, 'Invalid email address'), }) .superRefine((v, ctx) => { // End must not precede start. Compare on the folded date+time @@ -193,6 +203,8 @@ const form = useForm({ nostr_notifications: false, notification_subject: '', notification_body: '', + organizer_name: '', + reply_to_email: '', } }) @@ -299,6 +311,8 @@ async function populateFromEvent(event: TicketedEvent) { nostr_notifications: event.extra?.nostr_notifications ?? false, notification_subject: event.extra?.notification_subject ?? '', notification_body: event.extra?.notification_body ?? '', + organizer_name: event.extra?.organizer_name ?? '', + reply_to_email: event.extra?.reply_to_email ?? '', }) selectedCategories.value = [...(event.categories ?? [])] if (event.banner) { @@ -425,6 +439,8 @@ const onSubmit = form.handleSubmit(async (formValues) => { nostr_notifications: formValues.nostr_notifications, notification_subject: formValues.notification_subject, notification_body: formValues.notification_body, + organizer_name: formValues.organizer_name, + reply_to_email: formValues.reply_to_email || null, } if (isEditMode.value) { @@ -851,6 +867,35 @@ const handleOpenChange = (open: boolean) => { + + + + + Organizer name + + + + + Ticket emails arrive from "Organizer name via {{ siteName }}". + + + + + + + + Reply-to email + + + + + Where attendee replies go. Leave blank to use your account email. + + + + diff --git a/src/modules/events/types/ticket.ts b/src/modules/events/types/ticket.ts index d42b3c2c..533db2f 100644 --- a/src/modules/events/types/ticket.ts +++ b/src/modules/events/types/ticket.ts @@ -26,6 +26,12 @@ export interface EventExtra { nostr_notifications: boolean notification_subject: string notification_body: string + /** Shown as " via " in the From line of ticket + * emails. Optional. */ + organizer_name?: string + /** Reply-To for ticket emails; null/absent = the organizer's LNbits + * account email. */ + reply_to_email?: string | null /** Rails the organizer enabled (`lightning`, `fiat`). Empty / absent * means the legacy rule: Lightning always, fiat iff `allow_fiat`. * Same shape as upstream v2's `extra.payment_methods`. */ -- 2.55.0 From 77b0bc557ceb2dca046fdbe7e5d29e0026724eb8 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sun, 13 Sep 2026 17:41:36 +0200 Subject: [PATCH 2/2] feat(events): "send me a copy of each ticket email" toggle in the event editor Pairs with aiolabs/events `extra.copy_to_organizer` (v1.6.1-aio.15): BCC of every ticket email to the reply-to address (or the account email). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo --- .../events/components/CreateEventDialog.vue | 16 ++++++++++++++++ src/modules/events/types/ticket.ts | 2 ++ 2 files changed, 18 insertions(+) diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index bb7f20e..1c25524 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -146,6 +146,7 @@ const formSchema = toTypedSchema( price_per_ticket: z.number().min(0).default(0), email_notifications: z.boolean().default(false), nostr_notifications: z.boolean().default(false), + copy_to_organizer: z.boolean().default(false), notification_subject: z.string().max(200).default(''), notification_body: z.string().max(2000).default(''), organizer_name: z.string().trim().max(100).default(''), @@ -201,6 +202,7 @@ const form = useForm({ price_per_ticket: 0, email_notifications: false, nostr_notifications: false, + copy_to_organizer: false, notification_subject: '', notification_body: '', organizer_name: '', @@ -309,6 +311,7 @@ async function populateFromEvent(event: TicketedEvent) { price_per_ticket: event.price_per_ticket ?? 0, email_notifications: event.extra?.email_notifications ?? false, nostr_notifications: event.extra?.nostr_notifications ?? false, + copy_to_organizer: event.extra?.copy_to_organizer ?? false, notification_subject: event.extra?.notification_subject ?? '', notification_body: event.extra?.notification_body ?? '', organizer_name: event.extra?.organizer_name ?? '', @@ -437,6 +440,7 @@ const onSubmit = form.handleSubmit(async (formValues) => { payment_methods: formValues.payment_methods, email_notifications: formValues.email_notifications, nostr_notifications: formValues.nostr_notifications, + copy_to_organizer: formValues.copy_to_organizer, notification_subject: formValues.notification_subject, notification_body: formValues.notification_body, organizer_name: formValues.organizer_name, @@ -842,6 +846,18 @@ const handleOpenChange = (open: boolean) => { + + + +
+ Send me a copy of each ticket email + Goes to the reply-to address below, or your account email. +
+ + + +
+
diff --git a/src/modules/events/types/ticket.ts b/src/modules/events/types/ticket.ts index 533db2f..f1b46fd 100644 --- a/src/modules/events/types/ticket.ts +++ b/src/modules/events/types/ticket.ts @@ -32,6 +32,8 @@ export interface EventExtra { /** Reply-To for ticket emails; null/absent = the organizer's LNbits * account email. */ reply_to_email?: string | null + /** BCC every ticket email to the reply-to address. */ + copy_to_organizer?: boolean /** Rails the organizer enabled (`lightning`, `fiat`). Empty / absent * means the legacy rule: Lightning always, fiat iff `allow_fiat`. * Same shape as upstream v2's `extra.payment_methods`. */ -- 2.55.0