diff --git a/app/blueprints/admin/email.py b/app/blueprints/admin/email.py index b0b54a05..6899debc 100644 --- a/app/blueprints/admin/email.py +++ b/app/blueprints/admin/email.py @@ -21,7 +21,7 @@ from wtforms import TextAreaField, SubmitField, StringField from wtforms.validators import InputRequired, Length from app.markdown import render_markdown -from app.tasks.emails import send_user_email +from app.tasks.emails import send_user_email, send_bulk_email as task_send_bulk from app.utils import rank_required, addAuditLog from . import bp from ...models import UserRank, User, AuditSeverity @@ -70,8 +70,7 @@ def send_bulk_email(): text = form.text.data html = render_markdown(text) - for user in User.query.filter(User.email.isnot(None)).all(): - send_user_email.delay(user.email, user.locale or "en", form.subject.data, text, html) + task_send_bulk.delay(form.subject.data, text, html) return redirect(url_for("admin.admin_page")) diff --git a/app/models/users.py b/app/models/users.py index f9aef0d3..3af2feca 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -309,6 +309,11 @@ class EmailSubscription(db.Model): self.blacklisted = False self.token = None + @property + def url(self): + from ..utils import abs_url_for + return abs_url_for('users.unsubscribe', token=self.token) + class NotificationType(enum.Enum): # Package / release / etc diff --git a/app/tasks/emails.py b/app/tasks/emails.py index 7dca096c..a74e8e75 100644 --- a/app/tasks/emails.py +++ b/app/tasks/emails.py @@ -14,9 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +from typing import Dict from flask import render_template, escape -from flask_babel import force_locale, gettext +from flask_babel import force_locale, gettext, lazy_gettext from flask_mail import Message from app import mail from app.models import Notification, db, EmailSubscription, User @@ -36,6 +37,10 @@ def get_email_subscription(email): return ret +def gen_headers(sub: EmailSubscription) -> Dict[str,str]: + return {"List-Help": f"<{abs_url_for('flatpage', path='help/faq/')}>", "List-Unsubscribe": f"<{sub.url}>"} + + @celery.task() def send_verify_email(email, token, locale): sub = get_email_subscription(email) @@ -43,16 +48,16 @@ def send_verify_email(email, token, locale): return with force_locale(locale or "en"): - msg = Message("Confirm email address", recipients=[email]) + msg = Message("Confirm email address", recipients=[email], extra_headers=gen_headers(sub)) msg.body = """ This email has been sent to you because someone (hopefully you) has entered your email address as a user's email. - + If it wasn't you, then just delete this email. - + If this was you, then please click this link to confirm the address: - + {} """.format(abs_url_for('users.verify_email', token=token)) @@ -67,7 +72,7 @@ def send_unsubscribe_verify(email, locale): return with force_locale(locale or "en"): - msg = Message("Confirm unsubscribe", recipients=[email]) + msg = Message("Confirm unsubscribe", recipients=[email], extra_headers=gen_headers(sub)) msg.body = """ We're sorry to see you go. You just need to do one more thing before your email is blacklisted. @@ -80,33 +85,33 @@ def send_unsubscribe_verify(email, locale): @celery.task(rate_limit="25/m") -def send_email_with_reason(email: str, locale: str, subject: str, text: str, html: str, reason: str): +def send_email_with_reason(email: str, locale: str, subject: str, text: str, html: str, reason: str, conn: any): sub = get_email_subscription(email) if sub.blacklisted: return with force_locale(locale or "en"): - from flask_mail import Message - msg = Message(subject, recipients=[email]) + msg = Message(subject, recipients=[email], extra_headers=gen_headers(sub)) msg.body = text html = html or f"
{escape(text)}
" msg.html = render_template("emails/base.html", subject=subject, content=html, reason=reason, sub=sub) - mail.send(msg) + if conn: + conn.send(msg) + else: + mail.send(msg) @celery.task(rate_limit="25/m") -def send_user_email(email: str, locale: str, subject: str, text: str, html=None): - with force_locale(locale or "en"): - return send_email_with_reason(email, locale, subject, text, html, - gettext("You are receiving this email because you are a registered user of ContentDB.")) +def send_user_email(email: str, locale: str, subject: str, text: str, html=None, conn=None): + return send_email_with_reason(email, locale, subject, text, html, + lazy_gettext("You are receiving this email because you are a registered user of ContentDB."), conn) @celery.task(rate_limit="25/m") def send_anon_email(email: str, locale: str, subject: str, text: str, html=None): - with force_locale(locale or "en"): - return send_email_with_reason(email, locale, subject, text, html, - gettext("You are receiving this email because someone (hopefully you) entered your email address as a user's email.")) + return send_email_with_reason(email, locale, subject, text, html, + lazy_gettext("You are receiving this email because someone (hopefully you) entered your email address as a user's email.")) def send_single_email(notification, locale): @@ -115,7 +120,7 @@ def send_single_email(notification, locale): return with force_locale(locale or "en"): - msg = Message(notification.title, recipients=[notification.user.email]) + msg = Message(notification.title, recipients=[notification.user.email], extra_headers=gen_headers(sub)) msg.body = """ New notification: {} @@ -187,3 +192,10 @@ def send_pending_notifications(): send_notification_digest(to_send, user.locale or "en") elif len(to_send) > 0: send_single_email(to_send[0], user.locale or "en") + + +@celery.task() +def send_bulk_email(subject: str, text: str, html=None): + with mail.connect() as conn: + for user in User.query.filter(User.email.isnot(None)).all(): + send_user_email(user.email, user.locale or "en", subject, text, html, conn) diff --git a/app/templates/emails/base.html b/app/templates/emails/base.html index 1ceb5a6f..5bf39d34 100644 --- a/app/templates/emails/base.html +++ b/app/templates/emails/base.html @@ -59,7 +59,7 @@

{% block footer %} {{ reason }}
- + {{ _("Unsubscribe") }} {% endblock %}