From c95a1c456b0b118489c5be005e2448ec255d4935 Mon Sep 17 00:00:00 2001 From: Kobe Date: Mon, 2 Jun 2025 14:55:50 +0200 Subject: [PATCH] sending email async with celery --- __pycache__/app.cpython-313.pyc | Bin 5740 -> 6001 bytes __pycache__/celery_worker.cpython-313.pyc | Bin 0 -> 2586 bytes app.py | 29 +++++- celery_worker.py | 51 +++++++++++ docker-compose.yml | 49 ++++++++++ entrypoint.sh | 8 ++ requirements.txt | 23 +++-- .../__pycache__/notification.cpython-313.pyc | Bin 14952 -> 15854 bytes utils/notification.py | 84 ++++++++++-------- 9 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 __pycache__/celery_worker.cpython-313.pyc create mode 100644 celery_worker.py diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 5b5c1860ca22815010cd714ca95b0e67ea6dc2c8..22801aee25b63871ce1df59a2a6b9e835275adc0 100644 GIT binary patch delta 1391 zcmah}&2Jk;6rb7kuGe0F#vgIrBwl~SuAym66DM)fptOwxiA_Ux8@0$0u1VIZsbjli zN2ooNA|U~ldWhywRFL=^s{R4(rJzb2Ks``a;#eUBLJ1!iPP}ol*homs;WuyI`@J{s zy?ML8oIE(?`rYZYBUqO{o4;Ce?YTm@xR-izT0@$ z8XI!0QR|d9^}0@O&^0b7?pqLgTcSH$1WWWTHwGBx1N0kCI+A|nPIsj@0GoUiaF2h@ zwDHM7$xKN>rNCiw2wjxu%a$y#DwebHe#UT*Vfr;Nu%o=6QzP_&#qZEunmflS5JOd1 zAv7#Fra5*peS7=5G>J8D7`-c*4Q%kGplMfQ%zvz}RSk>g)BJ^j6T)4VOS1m!J`aP^pP<1zSgHOgZK8*NmKqG-YJv!uPZaAwzsPjJsBi=ix;UX4u+r! z|H)=$=8lRaV=>S`gc zme^p1zPHU=$n+9G;^~ zuGa7vSPfgXR`2Mf{LT$SsMbikLs(bHG=1he193g>30$Cy?)!2#mJ4j@2>r=Dgilb> zqu>de@{Bl%kA)I$9DNx;dlZxS7%l%af50RqD<7bGImtgw+LyC5<$E+5X4g1cqzRqS%dOgG zJ1r3S49XNG zEWi9-lU&nDw^Q9hx)mD&Y3@S%SsZc!z~ISlgY-Qf(@AG`np?Yd{Z(=kj%*0pcc~GW c8~6c@9*77#K6L(OS;N?UP^F3BqJ=%x-?IM>;Q#;t delta 1353 zcmah}PfQb87=K@9h8fzSQ=o&uikC{hwvV zuBrHgF9IqCr6|7g>4diBu-}&YDZ-ZLD=KPIqUyjoX^haO6#by&kd!oxV!IxZFB@}Pq51v8e6wgj z8GQ=I@kc#E&R_INAAF5_!D5%-)NNeKEw5&{bNzc)S<3O!Do=={5xi@hp}NnF1f0WN zt8Npb9rYy zm-&u2lL7w?ZC=q_btRYOY5ID%h!o86At5T+;v(~DAu23sgskeA2Lsa}y#oJq2+i6J4+ zE5tWKe2f2tj~uFeBTyH~)8tI!kU31OJ7zl^#SL>DX7Q={01`OScHw-bk4nF;!V7D( zGm*o^KggB%TAtUPim+*CRth)PmNOH4l~kZv+Qc_) GWb7j?yW!{n diff --git a/__pycache__/celery_worker.cpython-313.pyc b/__pycache__/celery_worker.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e95e30b654d820cfa786d4e3eab1a71203c1344 GIT binary patch literal 2586 zcmc&#&2JM&6rcU@$J$Opz9D>MF+s(JCMhI9q9mmxP=eE_%El5BF58Vgb{21T-I;Ys ze5g_hwW1e3RFvGBW4Kft^Jjzt!W}?DYA@VCE~4esH|q_!6p3SJ?b(?(Z|1%Ez2AFV zZD@!fXyKnn=MM)F`khat1n`4h;%50STw&nU=ame;7xatOW)|Un`OpKoU$x(h)7Bh57{O!s7+8xq!=#jadZS#+-%qlAB?T z-brvg)h)xttjn7dd=Y0{-Ldsc4grI@1rD?A-dYyt41P4hE?LsE*fN&bHg9FYSoEYO zrWqf157z+n{3g}g&|QGIp7+GOoAHrnA!3JnOF(1k9B zHL=b@`0ZT?4RUXw*+NQqqiM-CDXR0Y2PLHPfl1qRYe=iLNUN7jcV0bhK@?PowCcgW z=w`MK;P0)QRZqiYOyKuUBO+*sh?+noO(b$k0FNc^F$s9y%icKXjRW2|qyLU zfG;7Yn&69`@%Yv|;*p7t^^WtfFS@hf&(k=VEBE?-^0=t(yy2>(WK+*=(|(8J_2J?` zcf-H>t>^enG%L;2uY;7g!P*P%sRH!x3M7O?=tn7WLO3c=5t0?2tyEJ{588_kp#%OY zS+I~3RWPi=lSiXcH_8bKVLBnQC_r3e_M*!qxJW`~GR0Po$p*<&7A#!y-V*3h9z<1D zms$V#a5@DkLeoiS4yPwDEx1lGJptMF1<$s04Md;ey~QOK)^*5DOV?TAeJJ;CsrG<5 z`XO|GEu%_v+e-KK?)%O0O6Sg%#T$!jZJV9_>m7GG`(H|^W#5D5IQ(8w&d3`rz0xm{ zkx-_d)*)DEg@5$|bYsH562+%1EWB=-kP;iAET)7;bVPoW4Ch}Hc^0VjWdlOzTFwom z0)u{H0lrc~F1MaB?5u@Vk0Uw5v`PfO3UCgoWkuxzoE=uD0qmgV?1zwK6t>;ku#gcma`#1GWXEZGervWLq9*tByFQvfx{8LI1)UlqaxZJDTCj}w6% zd@t^AZ1N5CBj^D6KVmIcr>;z`4z6{sDeF6KAGy7MbI0*}v7tw?<}07wiS1dX>$4k! z_hX+{VFUUfL2s@H-7BGlQ>fgknpB+wnDQ!@nm+VoKP<8k@Y{wZ@&TWP0fXAk1Wf;n zqc(U0-mrCHS=HET-#Umki5aY0~waNvLa?HntsD4(U34_jH}?g=4(n z6VLB<;D&3sW=1z$fMKrWVxS9a@R=B9l7HgT`6BL-;?7n<;K7X z;iue6;dC4GS(dOmPJ>L>BGRh6|BESMU{@o!w$(iU-7UJ0n>|jbB zyHj4K5?-8Z+zEmZ+usnL`<_c=fRA|eYCeZYYB3ulXFvvJ7|{jjo=JirJVrYnqumdY z@(?vX3!+H#1EuR%rEB&0U8QF^2$F`TtCK%YUMp=2.0.0 +Flask-SQLAlchemy>=3.0.0 +Flask-Login>=0.6.0 +Flask-WTF>=1.0.0 +Flask-Migrate>=4.0.0 +SQLAlchemy>=1.4.0 +Werkzeug>=2.0.0 WTForms==3.1.1 -python-dotenv==1.0.1 +python-dotenv>=0.19.0 psycopg2-binary==2.9.9 gunicorn==21.2.0 -email_validator==2.1.0.post1 \ No newline at end of file +email_validator==2.1.0.post1 +celery>=5.3.0 +redis>=4.5.0 +alembic>=1.7.0 +flower>=2.0.0 +prometheus-client>=0.16.0 \ No newline at end of file diff --git a/utils/__pycache__/notification.cpython-313.pyc b/utils/__pycache__/notification.cpython-313.pyc index b711e82abf541be34aeb8a661ffaadde6bad0cc2..c6b6d5d9249d1dd8cec3bd2846f1e91d1d2c5920 100644 GIT binary patch delta 4313 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 Optional[Dict[str, Any]]: logger.error(f"Error retrieving SMTP settings: {str(e)}") return None -def send_email_via_smtp(mail: Mail) -> bool: - """ - Send an email using the configured SMTP settings. - - Args: - mail: The Mail object containing the email details - - Returns: - bool: True if email was sent successfully, False otherwise - """ - smtp_settings = get_smtp_settings() - if not smtp_settings: - logger.error("Cannot send email: SMTP settings not configured") - return False - +@celery.task +def send_email_task(mail_id: int): + """Celery task to send an email asynchronously""" try: + # Get the mail record + mail = Mail.query.get(mail_id) + if not mail: + logger.error(f"Mail record not found for ID: {mail_id}") + return False + + # Get SMTP settings + smtp_settings = get_smtp_settings() + if not smtp_settings: + logger.error("SMTP settings not found") + mail.status = 'failed' + mail.error_message = "SMTP settings not found" + db.session.commit() + return False + # Create message msg = MIMEMultipart() - msg['From'] = f"{smtp_settings['smtp_from_name']} <{smtp_settings['smtp_from_email']}>" - msg['To'] = mail.recipient + msg['From'] = smtp_settings.sender_email + msg['To'] = mail.recipient_email msg['Subject'] = mail.subject + msg['Date'] = formatdate(localtime=True) - # Attach HTML body - msg.attach(MIMEText(mail.body, 'html')) - - # Create SMTP connection - if smtp_settings['smtp_security'] == 'ssl': - server = smtplib.SMTP_SSL(smtp_settings['smtp_host'], int(smtp_settings['smtp_port'])) - else: - server = smtplib.SMTP(smtp_settings['smtp_host'], int(smtp_settings['smtp_port'])) - if smtp_settings['smtp_security'] == 'tls': - server.starttls() - - # Login if credentials are provided - if smtp_settings['smtp_username'] and smtp_settings['smtp_password']: - server.login(smtp_settings['smtp_username'], smtp_settings['smtp_password']) + # Add HTML content + msg.attach(MIMEText(mail.content, 'html')) # Send email - server.send_message(msg) - server.quit() + with smtplib.SMTP(smtp_settings.smtp_server, smtp_settings.smtp_port) as server: + if smtp_settings.use_tls: + server.starttls() + if smtp_settings.username and smtp_settings.password: + server.login(smtp_settings.username, smtp_settings.password) + server.send_message(msg) # Update mail status mail.status = 'sent' mail.sent_at = datetime.utcnow() db.session.commit() - - logger.info(f"Email sent successfully to {mail.recipient}") return True except Exception as e: logger.error(f"Error sending email: {str(e)}") + if mail: + mail.status = 'failed' + mail.error_message = str(e) + db.session.commit() + return False + +def send_email_via_smtp(mail: Mail) -> bool: + """Queue an email to be sent asynchronously""" + try: + # Queue the email sending task + send_email_task.delay(mail.id) + return True + except Exception as e: + logger.error(f"Error queueing email: {str(e)}") mail.status = 'failed' mail.error_message = str(e) db.session.commit()