Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 41ac128

Browse files
Split multiplart email sending into a dedicated handler (#9977)
Co-authored-by: Andrew Morgan <[email protected]>
1 parent 6660912 commit 41ac128

File tree

5 files changed

+122
-90
lines changed

5 files changed

+122
-90
lines changed

changelog.d/9977.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Split multipart email sending into a dedicated handler.

synapse/handlers/account_validity.py

Lines changed: 10 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@
1515
import email.mime.multipart
1616
import email.utils
1717
import logging
18-
from email.mime.multipart import MIMEMultipart
19-
from email.mime.text import MIMEText
2018
from typing import TYPE_CHECKING, List, Optional, Tuple
2119

2220
from synapse.api.errors import StoreError, SynapseError
23-
from synapse.logging.context import make_deferred_yieldable
2421
from synapse.metrics.background_process_metrics import wrap_as_background_process
2522
from synapse.types import UserID
2623
from synapse.util import stringutils
@@ -36,9 +33,11 @@ def __init__(self, hs: "HomeServer"):
3633
self.hs = hs
3734
self.config = hs.config
3835
self.store = self.hs.get_datastore()
39-
self.sendmail = self.hs.get_sendmail()
36+
self.send_email_handler = self.hs.get_send_email_handler()
4037
self.clock = self.hs.get_clock()
4138

39+
self._app_name = self.hs.config.email_app_name
40+
4241
self._account_validity_enabled = (
4342
hs.config.account_validity.account_validity_enabled
4443
)
@@ -63,23 +62,10 @@ def __init__(self, hs: "HomeServer"):
6362
self._template_text = (
6463
hs.config.account_validity.account_validity_template_text
6564
)
66-
account_validity_renew_email_subject = (
65+
self._renew_email_subject = (
6766
hs.config.account_validity.account_validity_renew_email_subject
6867
)
6968

70-
try:
71-
app_name = hs.config.email_app_name
72-
73-
self._subject = account_validity_renew_email_subject % {"app": app_name}
74-
75-
self._from_string = hs.config.email_notif_from % {"app": app_name}
76-
except Exception:
77-
# If substitution failed, fall back to the bare strings.
78-
self._subject = account_validity_renew_email_subject
79-
self._from_string = hs.config.email_notif_from
80-
81-
self._raw_from = email.utils.parseaddr(self._from_string)[1]
82-
8369
# Check the renewal emails to send and send them every 30min.
8470
if hs.config.run_background_tasks:
8571
self.clock.looping_call(self._send_renewal_emails, 30 * 60 * 1000)
@@ -159,38 +145,17 @@ async def _send_renewal_email(self, user_id: str, expiration_ts: int) -> None:
159145
}
160146

161147
html_text = self._template_html.render(**template_vars)
162-
html_part = MIMEText(html_text, "html", "utf8")
163-
164148
plain_text = self._template_text.render(**template_vars)
165-
text_part = MIMEText(plain_text, "plain", "utf8")
166149

167150
for address in addresses:
168151
raw_to = email.utils.parseaddr(address)[1]
169152

170-
multipart_msg = MIMEMultipart("alternative")
171-
multipart_msg["Subject"] = self._subject
172-
multipart_msg["From"] = self._from_string
173-
multipart_msg["To"] = address
174-
multipart_msg["Date"] = email.utils.formatdate()
175-
multipart_msg["Message-ID"] = email.utils.make_msgid()
176-
multipart_msg.attach(text_part)
177-
multipart_msg.attach(html_part)
178-
179-
logger.info("Sending renewal email to %s", address)
180-
181-
await make_deferred_yieldable(
182-
self.sendmail(
183-
self.hs.config.email_smtp_host,
184-
self._raw_from,
185-
raw_to,
186-
multipart_msg.as_string().encode("utf8"),
187-
reactor=self.hs.get_reactor(),
188-
port=self.hs.config.email_smtp_port,
189-
requireAuthentication=self.hs.config.email_smtp_user is not None,
190-
username=self.hs.config.email_smtp_user,
191-
password=self.hs.config.email_smtp_pass,
192-
requireTransportSecurity=self.hs.config.require_transport_security,
193-
)
153+
await self.send_email_handler.send_email(
154+
email_address=raw_to,
155+
subject=self._renew_email_subject,
156+
app_name=self._app_name,
157+
html=html_text,
158+
text=plain_text,
194159
)
195160

196161
await self.store.set_renewal_mail_status(user_id=user_id, email_sent=True)

synapse/handlers/send_email.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright 2021 The Matrix.org C.I.C. Foundation
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import email.utils
16+
import logging
17+
from email.mime.multipart import MIMEMultipart
18+
from email.mime.text import MIMEText
19+
from typing import TYPE_CHECKING
20+
21+
from synapse.logging.context import make_deferred_yieldable
22+
23+
if TYPE_CHECKING:
24+
from synapse.server import HomeServer
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
class SendEmailHandler:
30+
def __init__(self, hs: "HomeServer"):
31+
self.hs = hs
32+
33+
self._sendmail = hs.get_sendmail()
34+
self._reactor = hs.get_reactor()
35+
36+
self._from = hs.config.email.email_notif_from
37+
self._smtp_host = hs.config.email.email_smtp_host
38+
self._smtp_port = hs.config.email.email_smtp_port
39+
self._smtp_user = hs.config.email.email_smtp_user
40+
self._smtp_pass = hs.config.email.email_smtp_pass
41+
self._require_transport_security = hs.config.email.require_transport_security
42+
43+
async def send_email(
44+
self,
45+
email_address: str,
46+
subject: str,
47+
app_name: str,
48+
html: str,
49+
text: str,
50+
) -> None:
51+
"""Send a multipart email with the given information.
52+
53+
Args:
54+
email_address: The address to send the email to.
55+
subject: The email's subject.
56+
app_name: The app name to include in the From header.
57+
html: The HTML content to include in the email.
58+
text: The plain text content to include in the email.
59+
"""
60+
try:
61+
from_string = self._from % {"app": app_name}
62+
except (KeyError, TypeError):
63+
from_string = self._from
64+
65+
raw_from = email.utils.parseaddr(from_string)[1]
66+
raw_to = email.utils.parseaddr(email_address)[1]
67+
68+
if raw_to == "":
69+
raise RuntimeError("Invalid 'to' address")
70+
71+
html_part = MIMEText(html, "html", "utf8")
72+
text_part = MIMEText(text, "plain", "utf8")
73+
74+
multipart_msg = MIMEMultipart("alternative")
75+
multipart_msg["Subject"] = subject
76+
multipart_msg["From"] = from_string
77+
multipart_msg["To"] = email_address
78+
multipart_msg["Date"] = email.utils.formatdate()
79+
multipart_msg["Message-ID"] = email.utils.make_msgid()
80+
multipart_msg.attach(text_part)
81+
multipart_msg.attach(html_part)
82+
83+
logger.info("Sending email to %s" % email_address)
84+
85+
await make_deferred_yieldable(
86+
self._sendmail(
87+
self._smtp_host,
88+
raw_from,
89+
raw_to,
90+
multipart_msg.as_string().encode("utf8"),
91+
reactor=self._reactor,
92+
port=self._smtp_port,
93+
requireAuthentication=self._smtp_user is not None,
94+
username=self._smtp_user,
95+
password=self._smtp_pass,
96+
requireTransportSecurity=self._require_transport_security,
97+
)
98+
)

synapse/push/mailer.py

Lines changed: 8 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import email.mime.multipart
16-
import email.utils
1715
import logging
1816
import urllib.parse
19-
from email.mime.multipart import MIMEMultipart
20-
from email.mime.text import MIMEText
2117
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, TypeVar
2218

2319
import bleach
@@ -27,7 +23,6 @@
2723
from synapse.api.errors import StoreError
2824
from synapse.config.emailconfig import EmailSubjectConfig
2925
from synapse.events import EventBase
30-
from synapse.logging.context import make_deferred_yieldable
3126
from synapse.push.presentable_names import (
3227
calculate_room_name,
3328
descriptor_from_member_events,
@@ -108,7 +103,7 @@ def __init__(
108103
self.template_html = template_html
109104
self.template_text = template_text
110105

111-
self.sendmail = self.hs.get_sendmail()
106+
self.send_email_handler = hs.get_send_email_handler()
112107
self.store = self.hs.get_datastore()
113108
self.state_store = self.hs.get_storage().state
114109
self.macaroon_gen = self.hs.get_macaroon_generator()
@@ -310,17 +305,6 @@ async def send_email(
310305
self, email_address: str, subject: str, extra_template_vars: Dict[str, Any]
311306
) -> None:
312307
"""Send an email with the given information and template text"""
313-
try:
314-
from_string = self.hs.config.email_notif_from % {"app": self.app_name}
315-
except TypeError:
316-
from_string = self.hs.config.email_notif_from
317-
318-
raw_from = email.utils.parseaddr(from_string)[1]
319-
raw_to = email.utils.parseaddr(email_address)[1]
320-
321-
if raw_to == "":
322-
raise RuntimeError("Invalid 'to' address")
323-
324308
template_vars = {
325309
"app_name": self.app_name,
326310
"server_name": self.hs.config.server.server_name,
@@ -329,35 +313,14 @@ async def send_email(
329313
template_vars.update(extra_template_vars)
330314

331315
html_text = self.template_html.render(**template_vars)
332-
html_part = MIMEText(html_text, "html", "utf8")
333-
334316
plain_text = self.template_text.render(**template_vars)
335-
text_part = MIMEText(plain_text, "plain", "utf8")
336-
337-
multipart_msg = MIMEMultipart("alternative")
338-
multipart_msg["Subject"] = subject
339-
multipart_msg["From"] = from_string
340-
multipart_msg["To"] = email_address
341-
multipart_msg["Date"] = email.utils.formatdate()
342-
multipart_msg["Message-ID"] = email.utils.make_msgid()
343-
multipart_msg.attach(text_part)
344-
multipart_msg.attach(html_part)
345-
346-
logger.info("Sending email to %s" % email_address)
347-
348-
await make_deferred_yieldable(
349-
self.sendmail(
350-
self.hs.config.email_smtp_host,
351-
raw_from,
352-
raw_to,
353-
multipart_msg.as_string().encode("utf8"),
354-
reactor=self.hs.get_reactor(),
355-
port=self.hs.config.email_smtp_port,
356-
requireAuthentication=self.hs.config.email_smtp_user is not None,
357-
username=self.hs.config.email_smtp_user,
358-
password=self.hs.config.email_smtp_pass,
359-
requireTransportSecurity=self.hs.config.require_transport_security,
360-
)
317+
318+
await self.send_email_handler.send_email(
319+
email_address=email_address,
320+
subject=subject,
321+
app_name=self.app_name,
322+
html=html_text,
323+
text=plain_text,
361324
)
362325

363326
async def _get_room_vars(

synapse/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
from synapse.handlers.room_member import RoomMemberHandler, RoomMemberMasterHandler
105105
from synapse.handlers.room_member_worker import RoomMemberWorkerHandler
106106
from synapse.handlers.search import SearchHandler
107+
from synapse.handlers.send_email import SendEmailHandler
107108
from synapse.handlers.set_password import SetPasswordHandler
108109
from synapse.handlers.space_summary import SpaceSummaryHandler
109110
from synapse.handlers.sso import SsoHandler
@@ -549,6 +550,10 @@ def get_deactivate_account_handler(self) -> DeactivateAccountHandler:
549550
def get_search_handler(self) -> SearchHandler:
550551
return SearchHandler(self)
551552

553+
@cache_in_self
554+
def get_send_email_handler(self) -> SendEmailHandler:
555+
return SendEmailHandler(self)
556+
552557
@cache_in_self
553558
def get_set_password_handler(self) -> SetPasswordHandler:
554559
return SetPasswordHandler(self)

0 commit comments

Comments
 (0)