Example #1
0
 def test_does_not_split_links(self):
     # token that causes html2text to make a line break if wrap_links option is not set
     token = '4522e1fa-9827-44ec-bf76-f7fa8fb132ff'
     url = 'http://localhost:8080/#/signup?invite={}&email=please%40join.com'.format(
         token)
     html = '<a href="{}">some link</a>'.format(url)
     text = generate_plaintext_from_html(html)
     self.assertIn(url, text)
Example #2
0
def inbound_received(sender, event, esp_name, **kwargs):
    incoming_message = event.message

    # check local part of reply-to and extract conversation and user (fail if they don't exist)
    local_part = incoming_message.to[0].username
    try:
        conversation_id, user_id, thread_id = parse_local_part(local_part)
    except (UnicodeDecodeError, binascii.Error):
        sentry_client.captureException()
        return
    user = get_user_model().objects.get(id=user_id)

    thread = None
    if thread_id is not None:
        thread = ConversationMessage.objects.get(id=thread_id)
        conversation = thread.conversation
    else:
        conversation = Conversation.objects.get(id=conversation_id)

    # get email content as plain text
    if incoming_message.text is not None:
        text_content = incoming_message.text
    elif incoming_message.html is not None:
        # let's just make HTML into plain text
        html_content = incoming_message.html
        text_content = generate_plaintext_from_html(html_content)
    else:
        # Inform the user if we couldn't find any content
        notify_about_rejected_email(user,
                                    'Karrot could not find any reply text')
        return

    # extract email reply text
    # Try out both talon and discourse email_reply_trimmer
    # Trimmers are conservative and sometimes keep more lines, leading to bloated replies.
    # We choose the trimmed reply that has fewer lines.

    trimmed_talon, line_count_talon = trim_with_talon(text_content)
    trimmed_discourse, line_count_discourse = trim_with_discourse(text_content)

    reply_plain = trimmed_discourse if line_count_discourse <= line_count_talon else trimmed_talon

    stats.incoming_email_trimmed({
        'line_count_original':
        len(text_content.splitlines()),
        'line_count_talon':
        line_count_talon,
        'line_count_discourse':
        line_count_discourse,
        'from_html':
        1 if incoming_message.text is None else 0
    })

    # add reply to conversation
    if conversation.is_closed:
        notify_about_rejected_email(user, reply_plain)
        return

    if not conversation.participants.filter(id=user.id).exists():
        notify_about_rejected_email(user, reply_plain)
        return

    created_message = ConversationMessage.objects.create(
        author=user,
        conversation=conversation,
        thread=thread,
        content=reply_plain,
        received_via='email',
    )

    incoming_message_serialized = dict(incoming_message)
    incoming_message_serialized['text'] = incoming_message.text
    incoming_message_serialized['html'] = incoming_message.html
    IncomingEmail.objects.create(
        user=user,
        message=created_message,
        payload=incoming_message_serialized,
        version=2,
    )
Example #3
0
    def post(self, request):
        """
        Receive conversation replies via e-mail
        Request payload spec: https://developers.sparkpost.com/api/relay-webhooks/#header-relay-webhook-payload
        """

        auth_key = request.META.get('HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN')
        if auth_key is None or auth_key != settings.SPARKPOST_RELAY_SECRET:
            return Response(
                status=status.HTTP_403_FORBIDDEN,
                data={
                    'message':
                    'Invalid HTTP_X_MESSAGESYSTEMS_WEBHOOK_TOKEN header'
                })

        for messages in [e['msys'].values() for e in request.data]:
            for incoming_message in messages:
                # 1. get email content and reply-to
                reply_to = parseaddr(incoming_message['rcpt_to'])[1]
                content = incoming_message['content']

                # 2. check local part of reply-to and extract conversation and user (fail if they don't exist)
                local_part = reply_to.split('@')[0]
                try:
                    conversation_id, user_id, thread_id = parse_local_part(
                        local_part)
                except (UnicodeDecodeError, binascii.Error):
                    sentry_client.captureException()
                    continue
                user = get_user_model().objects.get(id=user_id)

                thread = None
                if thread_id is not None:
                    thread = ConversationMessage.objects.get(id=thread_id)
                    conversation = thread.conversation
                else:
                    conversation = Conversation.objects.get(id=conversation_id)

                # 3. extract the email reply text
                # Try plain text first, most emails have that.
                if 'text' in content:
                    # Try out both talon and discourse email_reply_trimmer
                    # Trimmers are conservative and sometimes keep more lines, leading to bloated replies.
                    # We choose the trimmed reply that has fewer lines.
                    text_content = content['text']

                    trimmed_talon, line_count_talon = trim_with_talon(
                        text_content)
                    trimmed_discourse, line_count_discourse = trim_with_discourse(
                        text_content)

                    reply_plain = trimmed_discourse if line_count_discourse <= line_count_talon else trimmed_talon

                    stats.incoming_email_trimmed({
                        'line_count_original':
                        len(text_content.splitlines()),
                        'line_count_talon':
                        line_count_talon,
                        'line_count_discourse':
                        line_count_discourse,
                    })

                else:
                    # Fall back to html
                    try:
                        html_content = content['html']
                    except KeyError:
                        # Inform the user if we couldn't find any content
                        sentry_client.captureException()
                        notify_about_rejected_email(
                            user, 'Karrot could not find any reply text')
                        continue

                    reply_html = trim_html_with_talon(html_content)
                    reply_plain = generate_plaintext_from_html(reply_html)

                    stats.incoming_html_email_trimmed({
                        'length_original':
                        len(html_content.splitlines()),
                        'length_html_talon':
                        len(reply_html),
                        'length_plain_talon':
                        len(reply_plain),
                    })

                # 4. add reply to conversation
                if conversation.is_closed:
                    notify_about_rejected_email(user, reply_plain)
                    continue

                if not conversation.participants.filter(id=user.id).exists():
                    notify_about_rejected_email(user, reply_plain)
                    continue

                created_message = ConversationMessage.objects.create(
                    author=user,
                    conversation=conversation,
                    thread=thread,
                    content=reply_plain,
                    received_via='email',
                )

                IncomingEmail.objects.create(
                    user=user,
                    message=created_message,
                    payload=incoming_message,
                )

        return Response(status=status.HTTP_200_OK, data={})