from __future__ import annotations from celery import Celery from .settings import settings celery = Celery( "multimailer", broker=settings.redis_url, backend=settings.redis_url, ) celery.conf.update( task_default_queue="default", task_routes={ "multimailer.send_email": {"queue": "send_email"}, "multimailer.append_sent": {"queue": "append_sent"}, }, worker_prefetch_multiplier=1, task_acks_late=True, task_reject_on_worker_lost=True, ) @celery.task(name="multimailer.ping") def ping(): return "pong" @celery.task(name="multimailer.send_email", bind=True, max_retries=None) def send_email(self, job_id: str): """Send one queued campaign job. The task records all state changes in the database. Temporary SMTP/network failures are retried with the campaign's configured backoff. """ from app.db.models import CampaignVersion, JobSendStatus from app.db.session import SessionLocal from app.mailer.persistence.campaigns import load_version_config from app.mailer.sending.jobs import SendJobError, next_retry_delay, send_campaign_job from app.mailer.sending.smtp import SmtpSendError with SessionLocal() as session: try: return send_campaign_job(session, job_id=job_id, enqueue_imap_task=True).as_dict() except SmtpSendError as exc: # send_campaign_job has already updated the job attempt/status. from app.db.models import CampaignJob job = session.get(CampaignJob, job_id) if job and job.send_status == JobSendStatus.FAILED_TEMPORARY.value: version = session.get(CampaignVersion, job.campaign_version_id) delay = 60 if version: try: _, _, config = load_version_config(session, version.id) delay = next_retry_delay(config, job.attempt_count) except Exception: delay = 60 raise self.retry(exc=exc, countdown=delay) raise except SendJobError: raise @celery.task(name="multimailer.append_sent", bind=True, max_retries=None) def append_sent(self, job_id: str): """Append the exact sent MIME to the configured IMAP Sent folder.""" from app.db.session import SessionLocal from app.mailer.sending.imap import ImapAppendError from app.mailer.sending.jobs import append_sent_for_job with SessionLocal() as session: try: return append_sent_for_job(session, job_id=job_id).as_dict() except ImapAppendError as exc: if getattr(exc, "temporary", None) is True: raise self.retry(exc=exc, countdown=300) raise