Skip to content

Commit a96397d

Browse files
committed
Rate-limit emails sent by the same user in a period of time
1 parent 9714f73 commit a96397d

File tree

2 files changed

+60
-35
lines changed

2 files changed

+60
-35
lines changed

Diff for: h/services/email.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
logger = get_task_logger(__name__)
1818

1919
# Limit for the number of mention emails sent by a single user in a day to prevent abuse
20-
DAILY_SENDER_MENTION_LIMIT = 5
20+
DAILY_SENDER_MENTION_LIMIT = 100
2121

2222

2323
@dataclass(frozen=True)
@@ -91,8 +91,10 @@ def _allow_sending(self, task_data: TaskData) -> bool:
9191
and self._sender_limit_reached(task_data)
9292
):
9393
logger.warning(
94-
"Email not sent for sender_id=%s: limit reached",
94+
"Not sending email: tag=%r sender_id=%s recipient_ids=%s. Sender limit reached.",
95+
task_data.tag,
9596
task_data.sender_id,
97+
task_data.recipient_ids,
9698
)
9799
return False
98100
return True

Diff for: tests/unit/h/services/email_test.py

+56-33
Original file line numberDiff line numberDiff line change
@@ -69,26 +69,22 @@ def test_send_creates_email_message_with_subaccount(
6969
extra_headers={"X-MC-Tags": EmailTag.TEST, "X-MC-Subaccount": "subaccount"},
7070
)
7171

72-
def test_send_creates_email_with_mention(
73-
self, email_data, task_data, email_service, pyramid_mailer, task_done_service
72+
def test_send_creates_mention_email_when_sender_limit_not_reached(
73+
self,
74+
mention_email_data,
75+
mention_task_data,
76+
email_service,
77+
pyramid_mailer,
78+
task_done_service,
7479
):
75-
email_data = EmailData(
76-
recipients=["[email protected]"],
77-
subject="My email subject",
78-
body="Some text body",
79-
tag=EmailTag.MENTION_NOTIFICATION,
80-
)
81-
task_data = TaskData(
82-
tag=email_data.tag,
83-
sender_id=123,
84-
recipient_ids=[124],
80+
task_done_service.sender_mention_count.return_value = (
81+
DAILY_SENDER_MENTION_LIMIT - 1
8582
)
86-
task_done_service.sender_mention_count.return_value = DAILY_SENDER_MENTION_LIMIT
8783

88-
email_service.send(email_data, task_data)
84+
email_service.send(mention_email_data, mention_task_data)
8985

9086
task_done_service.sender_mention_count.assert_called_once_with(
91-
task_data.sender_id, mock.ANY
87+
mention_task_data.sender_id, mock.ANY
9288
)
9389
pyramid_mailer.message.Message.assert_called_once_with(
9490
recipients=["[email protected]"],
@@ -98,28 +94,20 @@ def test_send_creates_email_with_mention(
9894
extra_headers={"X-MC-Tags": EmailTag.MENTION_NOTIFICATION},
9995
)
10096

101-
def test_send_does_not_create_email_with_mention(
102-
self, email_data, task_data, email_service, pyramid_mailer, task_done_service
97+
def test_send_does_not_create_mention_email_when_sender_limit_reached(
98+
self,
99+
mention_email_data,
100+
mention_task_data,
101+
email_service,
102+
pyramid_mailer,
103+
task_done_service,
103104
):
104-
email_data = EmailData(
105-
recipients=["[email protected]"],
106-
subject="My email subject",
107-
body="Some text body",
108-
tag=EmailTag.MENTION_NOTIFICATION,
109-
)
110-
task_data = TaskData(
111-
tag=email_data.tag,
112-
sender_id=123,
113-
recipient_ids=[124],
114-
)
115-
task_done_service.sender_mention_count.return_value = (
116-
DAILY_SENDER_MENTION_LIMIT + 1
117-
)
105+
task_done_service.sender_mention_count.return_value = DAILY_SENDER_MENTION_LIMIT
118106

119-
email_service.send(email_data, task_data)
107+
email_service.send(mention_email_data, mention_task_data)
120108

121109
task_done_service.sender_mention_count.assert_called_once_with(
122-
task_data.sender_id, mock.ANY
110+
mention_task_data.sender_id, mock.ANY
123111
)
124112
pyramid_mailer.message.Message.assert_not_called()
125113

@@ -159,12 +147,29 @@ def test_send_logging_with_extra(self, email_data, email_service, info_caplog):
159147
recipient_ids=[recipient_id],
160148
extra={"annotation_id": annotation_id},
161149
)
150+
162151
email_service.send(email_data, task_data)
163152

164153
assert info_caplog.messages == [
165154
f"Sent email: tag={task_data.tag!r}, sender_id={sender_id}, recipient_ids={[recipient_id]}, annotation_id={annotation_id!r}"
166155
]
167156

157+
def test_sender_limit_reached_logging(
158+
self,
159+
mention_email_data,
160+
mention_task_data,
161+
email_service,
162+
task_done_service,
163+
info_caplog,
164+
):
165+
task_done_service.sender_mention_count.return_value = DAILY_SENDER_MENTION_LIMIT
166+
167+
email_service.send(mention_email_data, mention_task_data)
168+
169+
assert info_caplog.messages == [
170+
f"Not sending email: tag={mention_task_data.tag!r} sender_id={mention_task_data.sender_id} recipient_ids={mention_task_data.recipient_ids}. Sender limit reached."
171+
]
172+
168173
def test_send_creates_task_done(
169174
self, email_data, task_data, email_service, task_done_service
170175
):
@@ -174,6 +179,7 @@ def test_send_creates_task_done(
174179
recipient_ids=[124],
175180
extra={"annotation_id": "annotation_id"},
176181
)
182+
177183
email_service.send(email_data, task_data)
178184

179185
task_done_service.create.assert_called_once_with(task_data)
@@ -195,6 +201,23 @@ def task_data(self):
195201
recipient_ids=[124],
196202
)
197203

204+
@pytest.fixture
205+
def mention_email_data(self):
206+
return EmailData(
207+
recipients=["[email protected]"],
208+
subject="My email subject",
209+
body="Some text body",
210+
tag=EmailTag.MENTION_NOTIFICATION,
211+
)
212+
213+
@pytest.fixture
214+
def mention_task_data(self):
215+
return TaskData(
216+
tag=EmailTag.MENTION_NOTIFICATION,
217+
sender_id=123,
218+
recipient_ids=[124],
219+
)
220+
198221
@pytest.fixture
199222
def email_service(self, pyramid_request, pyramid_mailer, task_done_service):
200223
request_mailer = pyramid_mailer.get_mailer.return_value

0 commit comments

Comments
 (0)