Skip to content

Commit e636c57

Browse files
wes-otffrjo
andauthored
Sending e-mail async with celery (#4483)
This does a full implementation of celery for sending emails. The functionality was already partially implemented but wasn't properly configured to be used app-wide nor was it ever enabled. **Running locally** 1. Get a Redis instance setup locally (I used docker, ie. `docker run -d -p 6379:6379 redis`) 2. Install requirements 3. Configuration - `SEND_MESSAGES=True` so emails will appear in terminal - `CELERY_TASK_ALWAYS_EAGER=False` so celery will add tasks to the broker rather than run it immediately - `CELERY_BROKER_URL` & `CELERY_RESULT_BACKEND` [are properly set to your broker](https://docs.celeryq.dev/en/stable/getting-started/first-steps-with-celery.html) 4. In a separate terminal from the running `make serve` (but with same environment vars), run `celery -A hypha.celery worker -E --loglevel=info` to start celery Co-authored-by: Fredrik Jonsson <[email protected]>
1 parent c388726 commit e636c57

File tree

15 files changed

+291
-107
lines changed

15 files changed

+291
-107
lines changed

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
release: python manage.py migrate --noinput && python manage.py clear_cache --cache=default && python manage.py sync_roles
22
web: gunicorn hypha.wsgi:application --log-file -
3+
worker: celery worker --app=hypha.celery --autoscale=6,2 --events

docs/getting-started/architecture.md

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
1-
2-
## Diagram
3-
4-
```
5-
Integrations
6-
┌────────────┐ ┌────────────┐
7-
│ Email │ │ Slack │
8-
└────────────┘ └────────────┘
9-
┌────────────┐ ┌────────────┐
10-
│ Amazon S3 │ │ Sentry │
11-
└────────────┘ └────────────┘
12-
13-
14-
15-
┌────────────┐ Databases
16-
│ APPLY SITE │◀────┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─┐
17-
└────────────┘ │ ┌────────────┐ ╎ ┌────────────┐ ╎
18-
├──────│ Django / │◀───────╎ │ PostgreSQL │ ╎
19-
┌────────────┐ │ │ Wagtail │ ╎ └────────────┘ ╎
20-
│ WAGTAIL │◀────┘ └────────────┘ └─ ─ ─ ─ ─ ─ ─ ─ ┘
21-
│ ADMIN │
22-
└────────────┘
1+
```mermaid
2+
---
3+
title: Hypha Architecture Diagram
4+
---
5+
flowchart LR
6+
subgraph integrations[Integrations]
7+
email[Email]
8+
slack[Slack]
9+
amazon[Amazon S3]
10+
sentry[Sentry]
11+
redis[Redis]
12+
end
13+
postgres[(PostgreSQL)] --> django[Django/Wagtail]
14+
django --> apply[Apply Site]
15+
django --> wagtail[Wagtail Admin]
16+
django --> |"(Optional)"|celery[Celery]
2317
```
2418

2519
-----------
@@ -54,6 +48,10 @@ Media is encouraged to be split into two distinct storage locations. A Public an
5448

5549
Media should also be served from a view that inherits from the [PrivateMediaView](https://github.com/HyphaApp/hypha/blob/main/hypha/apply/utils/storage.py) which will confirm that the file isn't made public and can be configured to return the file object from an authenticated view.
5650

51+
### Celery
52+
53+
An optional addition to allow certain tasks (ie. email/slack sending, other slower operations) to run asynchronously. Requires a separate celery worker to be running
54+
5755

5856
## External Integrations
5957

@@ -68,3 +66,7 @@ If configured, Hypha is able to send out notifications to different slack channe
6866
### Email
6967

7068
Emails in Hypha are used for password recovery and sending out important notifications to the users.
69+
70+
### Redis
71+
72+
Hypha uses Redis as a message broker for Celery

docs/setup/administrators/configuration.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,36 @@ Good for testing, might not be a good idea in production.
135135

136136
----
137137

138+
Celery settings for operations such as email/slack sending
139+
140+
When enabled (default), all celery tasks will run synchronously and do not require a broker.
141+
142+
CELERY_TASK_ALWAYS_EAGER = env.bool("CELERY_TASK_ALWAYS_EAGER", True)
143+
144+
145+
Env var used by Heroku to set & update the Redis URL
146+
147+
REDIS_URL = env.str("REDIS_URL", None)
148+
149+
Used to set the TLS cert verification mode - needs to be `CERT_REQUIRED`, `CERT_OPTIONAL` or `CERT_NONE`
150+
151+
REDIS_SSL_CERT_REQS = env.str("REDIS_SSL_CERT_REQS", "CERT_REQUIRED")
152+
153+
Max connections allowed to Redis - defaulted to 20
154+
155+
CELERY_REDIS_MAX_CONNECTIONS = env.int("CELERY_REDIS_MAX_CONNECTIONS", 20)
156+
157+
Used to specify the broker Celery should use, for more info see the [Celery getting started docs](https://docs.celeryq.dev/en/stable/getting-started/first-steps-with-celery.html)
158+
159+
CELERY_BROKER_URL = env.str("CELERY_BROKER_URL", None)
160+
CELERY_RESULT_BACKEND = env.str("CELERY_RESULT_BACKEND", None)
161+
162+
!!! note
163+
164+
If a `REDIS_URL` value is specified it will automatically be set as both `CELERY_BROKER_URL` and `CELERY_RESULT_BACKEND`. This can be overridden by manually setting either `CELERY_BROKER_URL` or `CELERY_RESULT_BACKEND`. `REDIS_URL` & `REDIS_SSL_CERT_REQS` is primarily intended for use with Heroku's Key-Value Store, for more complex TLS needs it is recommend to manually set the result backend and broker url and configure TLS via URL params.
165+
166+
----
167+
138168
Organisation name and e-mail address etc., used in e-mail templates etc.
139169

140170
ORG_EMAIL = env.str('ORG_EMAIL', '[email protected]')

hypha/apply/activity/adapters/emails.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.template.loader import render_to_string
99
from django.utils.translation import gettext as _
1010

11+
from hypha.apply.activity import tasks
1112
from hypha.apply.activity.models import ALL, APPLICANT_PARTNERS, PARTNER
1213
from hypha.apply.projects.models.payment import CHANGES_REQUESTED_BY_STAFF, DECLINED
1314
from hypha.apply.projects.templatetags.project_tags import display_project_status
@@ -23,7 +24,6 @@
2324
)
2425

2526
from ..options import MESSAGES
26-
from ..tasks import send_mail
2727
from .base import AdapterBase
2828
from .utils import (
2929
get_compliance_email,
@@ -479,6 +479,6 @@ def send_message(self, message, source, subject, recipient, logs, **kwargs):
479479
logger.exception(e)
480480

481481
try:
482-
send_mail(subject, message, from_email, [recipient], logs=logs)
482+
tasks.send_mail(subject, message, from_email, [recipient], logs=logs)
483483
except Exception as e:
484484
return "Error: " + str(e)

hypha/apply/activity/tasks.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
from celery import Celery
1+
from celery import shared_task
22
from django.conf import settings
33
from django.core.mail import EmailMessage
44

5-
app = Celery("tasks")
6-
7-
app.config_from_object(settings, namespace="CELERY", force=True)
8-
95

106
def send_mail(subject, message, from_address, recipients, logs=None):
117
if settings.EMAIL_SUBJECT_PREFIX:
@@ -22,7 +18,7 @@ def send_mail(subject, message, from_address, recipients, logs=None):
2218
)
2319

2420

25-
@app.task
21+
@shared_task
2622
def send_mail_task(**kwargs):
2723
response = {"status": "", "id": None}
2824
email = EmailMessage(**kwargs)
@@ -42,7 +38,7 @@ def send_mail_task(**kwargs):
4238
return response
4339

4440

45-
@app.task
41+
@shared_task
4642
def update_message_status(response, message_pks):
4743
from .models import Message
4844

0 commit comments

Comments
 (0)