feat: per-event organizer name + reply-to on ticket emails
Some checks failed
lint.yml / feat: per-event organizer name + reply-to on ticket emails (pull_request) Failing after 0s
Some checks failed
lint.yml / feat: per-event organizer name + reply-to on ticket emails (pull_request) Failing after 0s
Each organizer is different, so the sender identity of ticket emails is now per event rather than per instance: - `extra.organizer_name` → From display name "Organizer via <site title>" (site title alone when unset). The From address stays the instance mailbox — that is what DKIM signs — so this costs nothing in mail auth. - `extra.reply_to_email` → Reply-To; blank falls back to the email on the LNbits account that owns the event wallet; no header when neither exists. When a reply-to exists the body says "Questions? Reply to this email and it reaches <organizer>", and "Organizer: <name>" is listed with the ticket details. Both fields sit next to the existing per-event subject/body in the admin dialog. The organizer's own wording (in whatever language) remains `notification_body`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
This commit is contained in:
parent
f835766935
commit
72b9c540f7
7 changed files with 193 additions and 25 deletions
|
|
@ -87,3 +87,59 @@ def test_payload_includes_event_details_and_qr(monkeypatch):
|
|||
assert "/events/api/v1/ticket-card/tkt1" in text
|
||||
assert "<img" not in html and "Ticket ID: tkt1" in html
|
||||
assert '<a href="https://lnbits.example/events/api/v1/ticket-card/tkt1">' in html
|
||||
|
||||
|
||||
def test_reply_to_and_organizer_sender_name(monkeypatch):
|
||||
from ..services import organizer_sender_name
|
||||
|
||||
monkeypatch.setattr(settings, "lnbits_site_title", "Oyez!")
|
||||
event = _event(
|
||||
extra={
|
||||
"organizer_name": "Château du Faune",
|
||||
"reply_to_email": "hello@faune.example",
|
||||
}
|
||||
)
|
||||
assert organizer_sender_name(event) == "Château du Faune via Oyez!"
|
||||
assert organizer_sender_name(_event()) == "Oyez!"
|
||||
|
||||
msg = build_ticket_email(
|
||||
"tickets@example.org",
|
||||
["ada@example.com"],
|
||||
"Subj",
|
||||
"text",
|
||||
None,
|
||||
reply_to="hello@faune.example",
|
||||
sender_name=organizer_sender_name(event),
|
||||
)
|
||||
assert msg["Reply-To"] == "hello@faune.example"
|
||||
# Non-ASCII display names are RFC 2047-encoded on the wire; decode to compare.
|
||||
from email.header import decode_header, make_header
|
||||
|
||||
assert str(make_header(decode_header(msg["From"]))) == (
|
||||
"Château du Faune via Oyez! <tickets@example.org>"
|
||||
)
|
||||
|
||||
no_reply = build_ticket_email(
|
||||
"tickets@example.org", ["ada@example.com"], "Subj", "text"
|
||||
)
|
||||
assert no_reply["Reply-To"] is None
|
||||
|
||||
|
||||
def test_reply_hint_only_when_reply_to_exists(monkeypatch):
|
||||
monkeypatch.setattr(settings, "lnbits_baseurl", "https://lnbits.example/")
|
||||
event = _event(extra={"organizer_name": "Château du Faune"})
|
||||
_, text, _ = _ticket_notification_payload(
|
||||
_ticket(), event, reply_to="hello@faune.example"
|
||||
)
|
||||
assert "Organizer: Château du Faune" in text
|
||||
assert "Reply to this email and it reaches Château du Faune" in text
|
||||
_, text, _ = _ticket_notification_payload(_ticket(), event, reply_to=None)
|
||||
assert "Reply to this email" not in text
|
||||
|
||||
|
||||
def test_blank_reply_to_email_is_none():
|
||||
from ..models import EventExtra
|
||||
|
||||
assert EventExtra(reply_to_email="").reply_to_email is None
|
||||
assert EventExtra(reply_to_email=" ").reply_to_email is None
|
||||
assert EventExtra(reply_to_email="a@b.example").reply_to_email == "a@b.example"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue