mirror of
https://github.com/pretix/pretix.git
synced 2026-05-21 17:54:08 +00:00
Outbox view
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
# Generated by Django 4.2.26 on 2026-01-22 13:44
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@@ -23,6 +24,7 @@ class Migration(migrations.Migration):
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("guid", models.UUIDField(db_index=True, default=uuid.uuid4)),
|
||||
("status", models.CharField(default="queued", max_length=200)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("sent", models.DateTimeField(blank=True, null=True)),
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import uuid
|
||||
|
||||
from django.core.mail import get_connection
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@@ -44,14 +46,17 @@ class OutgoingMail(models.Model):
|
||||
STATUS_AWAWITING_RETRY = "awaiting_retry"
|
||||
STATUS_FAILED = "failed"
|
||||
STATUS_SENT = "sent"
|
||||
STATUS_BOUNCED = "bounced"
|
||||
STATUS_CHOICES = (
|
||||
(STATUS_QUEUED, _("queued")),
|
||||
(STATUS_INFLIGHT, _("being sent")),
|
||||
(STATUS_AWAWITING_RETRY, _("awaiting retry")),
|
||||
(STATUS_FAILED, _("failed")),
|
||||
(STATUS_SENT, _("sent")),
|
||||
(STATUS_BOUNCED, _("bounced")),
|
||||
)
|
||||
|
||||
guid = models.UUIDField(db_index=True, default=uuid.uuid4)
|
||||
status = models.CharField(max_length=200, choices=STATUS_CHOICES, default=STATUS_QUEUED)
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
sent = models.DateTimeField(null=True, blank=True)
|
||||
@@ -157,6 +162,10 @@ class OutgoingMail(models.Model):
|
||||
else:
|
||||
return scopes_disabled() # noqa
|
||||
|
||||
@property
|
||||
def is_failed(self):
|
||||
return self.status in (OutgoingMail.STATUS_FAILED, OutgoingMail.STATUS_AWAWITING_RETRY, OutgoingMail.STATUS_BOUNCED)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.orderposition_id and not self.order_id:
|
||||
self.order = self.orderposition.order
|
||||
|
||||
@@ -39,6 +39,7 @@ import mimetypes
|
||||
import os
|
||||
import re
|
||||
import smtplib
|
||||
import uuid
|
||||
import warnings
|
||||
from datetime import timedelta
|
||||
from email.mime.image import MIMEImage
|
||||
@@ -213,10 +214,12 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
settings_holder = event or organizer
|
||||
|
||||
headers = headers or {}
|
||||
guid = uuid.uuid4()
|
||||
if auto_email:
|
||||
headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN'
|
||||
headers['Auto-Submitted'] = 'auto-generated'
|
||||
headers.setdefault('X-Mailer', 'pretix')
|
||||
headers.setdefault('X-PX-Correlation', str(guid))
|
||||
|
||||
bcc = list(bcc or [])
|
||||
if settings_holder and settings_holder.settings.mail_bcc:
|
||||
@@ -301,9 +304,9 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
orderposition=position,
|
||||
customer=customer,
|
||||
user=user,
|
||||
to=[email] if isinstance(email, str) else list(email),
|
||||
cc=cc or [],
|
||||
bcc=bcc or [],
|
||||
to=[email.lower()] if isinstance(email, str) else [e.lower() for e in email],
|
||||
cc=[e.lower() for e in cc] if cc else [],
|
||||
bcc=[e.lower() for e in bcc] if bcc else [],
|
||||
subject=subject,
|
||||
body_plain=body_plain,
|
||||
body_html=body_html,
|
||||
@@ -395,7 +398,6 @@ def mail_send_task(self, *args, outgoing_mail: int) -> bool:
|
||||
|
||||
log_target, error_log_action_type = outgoing_mail.log_parameters()
|
||||
invoices_attached = []
|
||||
actual_attachments = []
|
||||
|
||||
with outgoing_mail.scope_manager():
|
||||
# Attach tickets
|
||||
@@ -566,21 +568,21 @@ def mail_send_task(self, *args, outgoing_mail: int) -> bool:
|
||||
|
||||
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after"])
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after", "actual_attachments"])
|
||||
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
|
||||
elif retry_strategy in ("microsoft_concurrency", "quick"):
|
||||
max_retries = 5
|
||||
retry_after = [10, 30, 60, 300, 900, 900][self.request.retries]
|
||||
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after"])
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after", "actual_attachments"])
|
||||
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
|
||||
|
||||
elif retry_strategy == "slow":
|
||||
retry_after = [60, 300, 600, 1200, 1800, 1800][self.request.retries]
|
||||
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||
outgoing_mail.retry_after = now() + timedelta(seconds=retry_after)
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after"])
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after", "actual_attachments"])
|
||||
self.retry(max_retries=5, countdown=retry_after) # throws RetryException, ends function flow
|
||||
|
||||
except MaxRetriesExceededError:
|
||||
@@ -605,14 +607,14 @@ def mail_send_task(self, *args, outgoing_mail: int) -> bool:
|
||||
outgoing_mail.status = OutgoingMail.STATUS_FAILED
|
||||
outgoing_mail.sent = now()
|
||||
outgoing_mail.retry_after = None
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after"])
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after", "actual_attachments"])
|
||||
return False
|
||||
|
||||
# If we reach this, it's a non-retryable error
|
||||
outgoing_mail.status = OutgoingMail.STATUS_FAILED
|
||||
outgoing_mail.sent = now()
|
||||
outgoing_mail.retry_after = None
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after"])
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "retry_after", "actual_attachments"])
|
||||
for i in invoices_to_mark_transmitted:
|
||||
i.set_transmission_failed(provider="email_pdf", data={
|
||||
"reason": "exception",
|
||||
@@ -634,7 +636,6 @@ def mail_send_task(self, *args, outgoing_mail: int) -> bool:
|
||||
outgoing_mail.status = OutgoingMail.STATUS_SENT
|
||||
outgoing_mail.error = None
|
||||
outgoing_mail.error_detail = None
|
||||
outgoing_mail.actual_attachments = actual_attachments
|
||||
outgoing_mail.sent = now()
|
||||
outgoing_mail.retry_after = None
|
||||
outgoing_mail.save(update_fields=["status", "error", "error_detail", "sent", "actual_attachments", "retry_after"])
|
||||
@@ -974,8 +975,13 @@ def retry_stuck_queued_mails(sender, **kwargs):
|
||||
return
|
||||
|
||||
for m in OutgoingMail.objects.filter(
|
||||
status=OutgoingMail.STATUS_QUEUED,
|
||||
created__lt=now() - timedelta(hours=1),
|
||||
Q(
|
||||
status=OutgoingMail.STATUS_QUEUED,
|
||||
created__lt=now() - timedelta(hours=1),
|
||||
) | Q(
|
||||
status=OutgoingMail.STATUS_AWAWITING_RETRY,
|
||||
retry_after__lt=now() - timedelta(hours=1),
|
||||
)
|
||||
):
|
||||
mail_send_task.apply_async(kwargs={"outgoing_mail": m.pk})
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import uuid
|
||||
|
||||
import css_inline
|
||||
from django.conf import settings
|
||||
from django.template.loader import get_template
|
||||
@@ -155,7 +157,9 @@ def send_notification_mail(notification: Notification, user: User):
|
||||
tpl_plain = get_template('pretixbase/email/notification.txt')
|
||||
body_plain = tpl_plain.render(ctx)
|
||||
|
||||
guid = uuid.uuid4()
|
||||
m = OutgoingMail.objects.create(
|
||||
guid=guid,
|
||||
user=user,
|
||||
to=[user.email],
|
||||
subject='[{}] {}: {}'.format(
|
||||
@@ -166,7 +170,12 @@ def send_notification_mail(notification: Notification, user: User):
|
||||
body_plain=body_plain,
|
||||
body_html=body_html,
|
||||
sender=settings.MAIL_FROM_NOTIFICATIONS,
|
||||
headers={},
|
||||
headers={
|
||||
'X-Auto-Response-Suppress': 'OOF, NRN, AutoReply, RN',
|
||||
'Auto-Submitted': 'auto-generated',
|
||||
'X-Mailer': 'pretix',
|
||||
'X-PX-Correlation': str(guid),
|
||||
},
|
||||
)
|
||||
mail_send_task.apply_async(kwargs={
|
||||
'outgoing_mail': m.pk,
|
||||
|
||||
Reference in New Issue
Block a user