diff --git a/engine/apps/email/inbound.py b/engine/apps/email/inbound.py index 76eec2aa..1780f00c 100644 --- a/engine/apps/email/inbound.py +++ b/engine/apps/email/inbound.py @@ -86,15 +86,18 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView): # First try envelope_recipient field. # According to AnymailInboundMessage it's provided not by all ESPs. if message.envelope_recipient: - try: - token, domain = message.envelope_recipient.split("@") - except ValueError: - logger.error( - f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}" - ) - return None - if domain == live_settings.INBOUND_EMAIL_DOMAIN: - return token + recipients = message.envelope_recipient.split(",") + for recipient in recipients: + # if there is more than one recipient, the first matching the expected domain will be used + try: + token, domain = recipient.strip().split("@") + except ValueError: + logger.error( + f"get_integration_token_from_request: envelope_recipient field has unexpected format: {message.envelope_recipient}" + ) + continue + if domain == live_settings.INBOUND_EMAIL_DOMAIN: + return token else: logger.info("get_integration_token_from_request: message.envelope_recipient is not present") """ @@ -152,7 +155,10 @@ class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView): def get_sender_from_email_message(self, email: AnymailInboundMessage) -> str: try: - sender = email.from_email.addr_spec + if isinstance(email.from_email, list): + sender = email.from_email[0].addr_spec + else: + sender = email.from_email.addr_spec except AnymailInvalidAddress as e: # wasn't able to parse email address from message, return raw value from "From" header logger.warning( diff --git a/engine/apps/email/tests/test_inbound_email.py b/engine/apps/email/tests/test_inbound_email.py index a47b19f9..bf6ae5ae 100644 --- a/engine/apps/email/tests/test_inbound_email.py +++ b/engine/apps/email/tests/test_inbound_email.py @@ -1,4 +1,5 @@ import json +from textwrap import dedent import pytest from anymail.inbound import AnymailInboundMessage @@ -9,8 +10,19 @@ from rest_framework.test import APIClient from apps.email.inbound import InboundEmailWebhookView +@pytest.mark.parametrize( + "recipients,expected", + [ + ("{token}@example.com", status.HTTP_200_OK), + ("{token}@example.com, another@example.com", status.HTTP_200_OK), + ("{token}@example.com, another@example.com", status.HTTP_200_OK), + ("{token}@other.com, {token}@example.com", status.HTTP_400_BAD_REQUEST), + ], +) @pytest.mark.django_db -def test_amazon_ses_provider_load(settings, make_organization_and_user_with_token, make_alert_receive_channel): +def test_amazon_ses_provider_load( + settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected +): settings.INBOUND_EMAIL_ESP = "amazon_ses" settings.INBOUND_EMAIL_DOMAIN = "example.com" @@ -19,10 +31,10 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke organization, _, token = make_organization_and_user_with_token() _ = make_alert_receive_channel(organization, token=dummy_channel_token) - recipient = f"{dummy_channel_token}@example.com" + recipients = recipients.format(token=dummy_channel_token) mime = f"""From: sender@example.com Subject: Dummy email message - To: {recipient} + To: {recipients} Content-Type: text/plain Hello! @@ -30,7 +42,7 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke message = { "notificationType": "Received", - "receipt": {"action": {"type": "SNS"}, "recipients": [recipient]}, + "receipt": {"action": {"type": "SNS"}, "recipients": recipients.split(",")}, "content": mime, } @@ -54,7 +66,62 @@ def test_amazon_ses_provider_load(settings, make_organization_and_user_with_toke HTTP_X_AMZ_SNS_MESSAGE_ID=dummy_sns_message_id, ) - assert response.status_code == status.HTTP_200_OK + assert response.status_code == expected + + +@pytest.mark.parametrize( + "recipients,expected", + [ + ("{token}@example.com", status.HTTP_200_OK), + ("{token}@example.com, another@example.com", status.HTTP_200_OK), + ("{token}@example.com, another@example.com", status.HTTP_200_OK), + ("{token}@other.com, {token}@example.com", status.HTTP_200_OK), + ("{token}@other.com, {token}@another.com", status.HTTP_400_BAD_REQUEST), + ], +) +@pytest.mark.django_db +def test_mailgun_provider_load( + settings, make_organization_and_user_with_token, make_alert_receive_channel, recipients, expected +): + settings.INBOUND_EMAIL_ESP = "mailgun" + settings.INBOUND_EMAIL_DOMAIN = "example.com" + settings.INBOUND_EMAIL_WEBHOOK_SECRET = "secret" + + dummy_channel_token = "dummy-channel-token" + + organization, _, token = make_organization_and_user_with_token() + _ = make_alert_receive_channel(organization, token=dummy_channel_token) + + recipients = recipients.format(token=dummy_channel_token) + raw_event = { + "token": "06c96bafc3f42a66b9edd546347a2fe18dc23461fe80dc52f0", + "timestamp": "1461261330", + "signature": "dbb05e62be402448b36ffb81f6abfb888273c95617aa077b4da355b25bab3670", + "recipient": "{recipients}".format(recipients=recipients), + "sender": "envelope-from@example.org", + "body-mime": dedent( + """\ + From: sender@example.com + Subject: Dummy email message + To: {recipients} + Content-Type: text/plain + + Hello! + --94eb2c05e174adb140055b6339c5-- + """.format( + recipients=recipients + ) + ), + } + + client = APIClient() + response = client.post( + reverse("integrations:inbound_email_webhook"), + data=raw_event, + HTTP_AUTHORIZATION=token, + ) + + assert response.status_code == expected @pytest.mark.parametrize(