Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inbound email integration #837

Merged
merged 25 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
13c3d5b
Add inbound email functionality
vstpme Nov 11, 2022
008c093
Merge branch 'dev' into vadimkerr/inbound-email
vstpme Nov 11, 2022
7d62445
move inbound email view to email app
vstpme Nov 11, 2022
e78f382
use /integrations/v1/inbound_email_webhook
vstpme Nov 11, 2022
3245ca9
remove outdated email config from zabbix integration
vstpme Nov 11, 2022
73035a0
add test
vstpme Nov 14, 2022
8f1479f
Merge branch 'dev' into vadimkerr/inbound-email
vstpme Jan 31, 2023
b2dd277
Merge branch 'dev' into vadimkerr/inbound-email
vstpme Mar 7, 2023
d3465d8
Merge remote-tracking branch 'origin/dev' into vadimkerr/inbound-email
Konstantinov-Innokentii Mar 13, 2023
7b6e7a8
Polish inbound_email
Konstantinov-Innokentii Mar 13, 2023
18e2076
Use only one way of getting token from email address
Konstantinov-Innokentii Mar 13, 2023
b712b83
Inbound email docs
Konstantinov-Innokentii Mar 13, 2023
5a6aab2
Update CHANGELOG.md
Konstantinov-Innokentii Mar 13, 2023
b8fa1d4
Add OSS docs about Inbound Email
Konstantinov-Innokentii Mar 14, 2023
ef3f363
Merge branch 'dev' into vadimkerr/inbound-email
Konstantinov-Innokentii Mar 14, 2023
2d462b9
Polishing.
Konstantinov-Innokentii Mar 14, 2023
68293e6
Merge remote-tracking branch 'origin/vadimkerr/inbound-email' into va…
Konstantinov-Innokentii Mar 14, 2023
2b1d613
Handle several email addresses
Konstantinov-Innokentii Mar 14, 2023
f6a81ff
Handle bcc
Konstantinov-Innokentii Mar 14, 2023
fcaf7df
Remove test_inbound_email
Konstantinov-Innokentii Mar 14, 2023
d1dd54e
Remove check of to/bcc if envelope_recipient is not provided
Konstantinov-Innokentii Mar 16, 2023
002e0a6
fix
Konstantinov-Innokentii Mar 16, 2023
99ec063
Merge branch 'dev' into vadimkerr/inbound-email
Konstantinov-Innokentii Mar 16, 2023
c795810
lint docs
Konstantinov-Innokentii Mar 16, 2023
2b52b63
Merge remote-tracking branch 'origin/vadimkerr/inbound-email' into va…
Konstantinov-Innokentii Mar 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
add test
  • Loading branch information
vstpme committed Nov 14, 2022
commit 73035a0f468b38aa58f1b9c3e40cac0bc842bc47
55 changes: 34 additions & 21 deletions engine/apps/email/inbound.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import logging

from anymail.exceptions import AnymailWebhookValidationFailure
from anymail.inbound import AnymailInboundMessage
from anymail.signals import AnymailInboundEvent
from anymail.webhooks import amazon_ses, mailgun, mailjet, mandrill, postal, postmark, sendgrid, sparkpost
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, HttpResponseNotAllowed
from rest_framework import status
from rest_framework.request import Request
from rest_framework.response import Response
Expand All @@ -30,53 +32,64 @@


def get_messages_from_esp_request(request: Request) -> list[AnymailInboundMessage]:
assert (
live_settings.INBOUND_EMAIL_ESP in INBOUND_EMAIL_ESP_OPTIONS.keys()
), f"INBOUND_EMAIL_ESP env variable must be on of the following: {INBOUND_EMAIL_ESP_OPTIONS.keys()}"
assert live_settings.INBOUND_EMAIL_WEBHOOK_SECRET, "INBOUND_EMAIL_WEBHOOK_SECRET env variable must be set"

view_class, secret_name = INBOUND_EMAIL_ESP_OPTIONS[live_settings.INBOUND_EMAIL_ESP]

kwargs = {secret_name: live_settings.INBOUND_EMAIL_WEBHOOK_SECRET} if secret_name else {}
view = view_class(**kwargs)

view.run_validators(request)
events = view.parse_events(request)
try:
view.run_validators(request)
events = view.parse_events(request)
except AnymailWebhookValidationFailure:
return []

return [event.message for event in events if isinstance(event, AnymailInboundEvent)]


class InboundEmailWebhookView(AlertChannelDefiningMixin, APIView):
def dispatch(self, request, *args, **kwargs):
if not live_settings.INBOUND_EMAIL_ESP or not live_settings.INBOUND_EMAIL_DOMAIN:
return Response(status=status.HTTP_400_BAD_REQUEST)
def dispatch(self, request):
# http_method_names can't be used due to how AlertChannelDefiningMixin is implemented
# todo: refactor AlertChannelDefiningMixin
if not request.method.lower() in ["head", "post"]:
return HttpResponseNotAllowed(permitted_methods=["head", "post"])

if not live_settings.INBOUND_EMAIL_ESP:
return HttpResponse(
f"INBOUND_EMAIL_ESP env variable must be set. Options: {INBOUND_EMAIL_ESP_OPTIONS.keys()}",
status=status.HTTP_400_BAD_REQUEST,
)

if not live_settings.INBOUND_EMAIL_DOMAIN:
return HttpResponse("INBOUND_EMAIL_DOMAIN env variable must be set", status=status.HTTP_400_BAD_REQUEST)

# Some ESPs verify the webhook with a HEAD request at configuration time
if request.method.lower() == "head":
return Response(status=status.HTTP_200_OK)
return HttpResponse(status=status.HTTP_200_OK)

message = get_messages_from_esp_request(request)[0]
messages = get_messages_from_esp_request(request)
if not messages:
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)

message = messages[0]
token_to = message.to[0].address.split("@")[0]
token_recipient = message.envelope_recipient.split("@")[0]

result = self.try_dispatch_with_token(token_to, request, args, kwargs)
result = self.try_dispatch_with_token(request, token_to)
if result:
return result

result = self.try_dispatch_with_token(token_recipient, request, args, kwargs)
if result:
return result
token_recipient = message.envelope_recipient.split("@")[0] if message.envelope_recipient else None
if token_recipient:
result = self.try_dispatch_with_token(request, token_recipient)
if result:
return result

raise PermissionDenied("Integration key was not found. Permission denied.")

def try_dispatch_with_token(self, token, request, args, kwargs):
def try_dispatch_with_token(self, request, token):
try:
kwargs["alert_channel_key"] = token
return super().dispatch(request, *args, **kwargs)
return super().dispatch(request, alert_channel_key=token)
except PermissionDenied:
logger.info(f"Permission denied for token: {token}")
kwargs.pop("alert_channel_key")
return None

def post(self, request, alert_receive_channel):
Expand Down
26 changes: 26 additions & 0 deletions engine/apps/email/tests/test_inbound_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient


@pytest.mark.django_db
def test_inbound_email_webhook_head(
settings,
make_organization,
make_user_for_organization,
make_token_for_organization,
make_alert_receive_channel,
make_alert_group,
make_alert,
make_user_notification_policy,
):
settings.FEATURE_INBOUND_EMAIL_ENABLED = True
settings.INBOUND_EMAIL_ESP = "mailgun"
settings.INBOUND_EMAIL_DOMAIN = "test.test"
client = APIClient()

url = reverse("integrations:inbound_email_webhook")
response = client.head(url)

assert response.status_code == status.HTTP_200_OK