pretalx.mail.domain.send
Functions 4
get_send_mail_exceptions(request)source
def get_send_mail_exceptions(request):
exceptions = [
result[1]
for result in request_pre_send.send_robust(
sender=request.event, request=request
)
if len(result) == 2 and isinstance(result[1], SendMailException)
]
if exceptions:
errors = [str(e) for e in exceptions]
return errors or [_("You cannot send emails at this time.")]
return None
send_draft(mail, *, requestor=None, orga: bool = True) -> NonesourceHand a saved DRAFT QueuedMail to the worker for delivery.
Requires mail.pk; for unsaved mails see send_transient().
def send_draft(mail, *, requestor=None, orga: bool = True) -> None:
"""Hand a saved DRAFT :class:`QueuedMail` to the worker for delivery.
Requires ``mail.pk``; for unsaved mails see :func:`send_transient`.
"""
if mail._state.adding:
raise RuntimeError("send_draft requires a persisted mail")
if mail.state in (QueuedMailStates.SENT, QueuedMailStates.SENDING):
raise ValidationError(
_("This email has been sent already. It cannot be sent again.")
)
event = mail.event
with transaction.atomic():
if event:
queuedmail_pre_send.send_robust(sender=event, mail=mail)
if mail.sent is not None or mail.state == QueuedMailStates.SENT:
# A pre_send signal handler did the sending; nothing left to do.
mail.state = QueuedMailStates.SENT
mail.save(update_fields=["state", "sent"])
return
mail.state = QueuedMailStates.SENDING
mail.error_data = None
mail.error_timestamp = None
mail.save(update_fields=["state", "error_data", "error_timestamp"])
# Dispatch outside the atomic block so the worker observes the
# SENDING row instead of racing the commit. Do not move this back
# inside the transaction.
try:
task_send_draft.apply_async(args=[mail.pk], ignore_result=True)
except (OSError, OperationalError) as exc:
mail.mark_failed(exc)
return
mail.log_action(
"pretalx.mail.sent",
person=requestor,
orga=orga,
data={
"to_speakers": [
(speaker.pk, speaker.effective_email)
for speaker in mail.to_speakers.select_related("user")
]
},
)
if event:
queuedmail_post_send.send_robust(sender=event, mail=mail)
send_system_mail(*, subject, text, to: str, event=None, locale=None, context_kwargs=None, safe_extra_context=None)sourceRender and dispatch a *pretalx → user* notification: password resets, password / email change confirmations, organiser team invites, pretalx.event.domain.mail.send_orga_mail.
Async — defers to task_send_transient(). Always pins to the global backend (a broken event SMTP must not block password resets); event only informs placeholder resolution and body styling. Not persisted, not signalled, no audit log.
Not for event-shaped correspondence (speaker invitations, schedule notifications, …) — those want the event's From: line; render via render_to_mail() or render_template_to_mail() and dispatch through send_draft() (after save_draft()) or send_transient().
def send_system_mail(
*,
subject,
text,
to: str,
event=None,
locale=None,
context_kwargs=None,
safe_extra_context=None,
):
"""Render and dispatch a *pretalx → user* notification: password
resets, password / email change confirmations, organiser team
invites, :func:`pretalx.event.domain.mail.send_orga_mail`.
Async — defers to :func:`task_send_transient`. Always pins to the
global backend (a broken event SMTP must not block password resets);
``event`` only informs placeholder resolution and body styling. Not
persisted, not signalled, no audit log.
Not for event-shaped correspondence (speaker invitations, schedule
notifications, …) — those want the event's From: line; render via
:func:`render_to_mail` or :func:`render_template_to_mail` and
dispatch through :func:`send_draft` (after :func:`save_draft`) or
:func:`send_transient`.
"""
mail = render_to_mail(
subject_template=subject,
text_template=text,
event=event,
locale=locale,
context_kwargs=context_kwargs,
safe_extra_context=safe_extra_context,
)
mail.to = to
send_transient(mail, force_global_backend=True)
send_transient(mail, *, force_global_backend: bool = False) -> NonesourceFire-and-forget delivery of an unsaved QueuedMail.
No row, no signals, no log entry.
Async — renders the body synchronously and schedules task_send_transient(), which calls pretalx.mail.domain.smtp.deliver_payload(). The worker uses mail.event's SMTP for delivery, unless force_global_backend=True (system mails: pin to global so delivery survives a broken event SMTP). Caller must set mail.to first. A broker outage is logged and swallowed; the in-memory mail.sent / mail.state flip is best-effort only.
def send_transient(mail, *, force_global_backend: bool = False) -> None:
"""Fire-and-forget delivery of an unsaved :class:`QueuedMail`. No
row, no signals, no log entry.
Async — renders the body synchronously and schedules
:func:`task_send_transient`, which calls
:func:`pretalx.mail.domain.smtp.deliver_payload`. The worker uses
``mail.event``'s SMTP for delivery, unless ``force_global_backend=True``
(system mails: pin to global so delivery survives a broken event SMTP).
Caller must set ``mail.to`` first. A broker outage is logged and
swallowed; the in-memory ``mail.sent`` / ``mail.state`` flip is
best-effort only.
"""
if not mail._state.adding:
raise RuntimeError("send_transient must not be called on a persisted mail")
assert_rendered(mail.subject, mail.text, mail.text_html)
recipients = to_recipients(mail.to)
if not recipients:
raise ValueError("send_transient called with empty mail.to")
event = None if force_global_backend else mail.event
try:
mail_tasks.task_send_transient.apply_async(
kwargs={
"to": recipients,
"subject": mail.prefixed_subject,
"body": delivery_text(mail),
"html": delivery_html(mail),
"reply_to": to_recipients(mail.reply_to),
"event_id": event.pk if event else None,
"cc": to_recipients(mail.cc),
"bcc": to_recipients(mail.bcc),
"attachments": mail.attachments,
},
ignore_result=True,
)
except (OSError, OperationalError):
logger.exception("Failed to queue transient mail to %s", recipients)
return
mail.sent = now()
mail.state = QueuedMailStates.SENT