Skip to content

Customization

Custom Incoming Message Task

When a new email arrives at EmailEngine, a webhook will be sent to the email integration to handle it. The handling is executed in a task that could be overriden by a custom one using Application Config. The same task will also be used by the recovery functions.

Getting Started

  1. Generate a new task in your solution using Lime Project.
  2. Register the task to override the standard incoming webhook task by adding it to the Optional Application Configuration.

Example Webhook Message

The incoming message looks something like below.

{
  "id": "AAAAAQAACLY",
  "uid": 2230,
  "path": "INBOX",
  "date": "2024-09-05T12:39:38.000Z",
  "flags": [],
  "unseen": true,
  "size": 66202,
  "subject": "Want to buy a pizza [1234]",
  "from": { "name": "Kim Young", "address": "[email protected]" },
  "to": [
    {
      "name": "Support",
      "address": "[email protected]"
    }
  ],
  "messageId": "<AS8PR08MB89955D313A2F291643AD9E6C9R2D2@AS8PR08MB8995.japrd368.prof.inlook.com>",
  "text": {
    "id": "AAAAAQAACLaTkaExkaEykA",
    "encodedSize": { "plain": 31, "html": 1300 }
  },
  "specialUse": "\\Inbox",
  "messageSpecialUse": "\\Inbox",
  "seemsLikeNew": true
}

Note

id and account_id are the internal EmailEngine ids that needs to be passed to the standard email integration import function. messageId is the global unique Message-ID. The text/html of the message is not included for performance reasons.

Example: Pizza Handler

For the solution "Pizza", a new task called incoming was created. It's purpose is to filter out emails containing the word "pizza" in the subject. These should not result in conversations, but rather just create history notes. If no "pizza" is present in the subject, the standard function import_message() that imports messages will be used instead.

Application Configuration

In the Application Configuration, the following parameter was added:

incoming_message_task: solution_pizza.tasks.tasks.incoming

Task Code

solution-pizza/solution_pizza/tasks/tasks.py

from lime_task import task
from limepkg_email.incoming_message import import_message

import logging


logger = logging.getLogger(__name__)


@task
def incoming(app, message, account_id):
    if "pizza" in message["subject"]:
        logger.info("Found pizza in subject, creating history note")

        history = app.limetypes.history()
        history.properties.type.set_by_key("comment")
        history.properties.note.value = (
            f'{message["from"]["name"]} wants to buy a pizza!'
        )
        uow = app.unit_of_work()
        uow.add(history)
        uow.commit()
    else:
        logger.info("No pizza present in subject, starting normal import")

        import_message(
            app,
            message,
            account_id,
        )

Trying It Out

  1. Restart the task handler after every code change.
  2. Send an email with the word "pizza" in the subject and expect a history note to be created.
  3. Send an email without "pizza" in the subject and expect a conversation message to be created.

Example: Inbox Migration

We want to offer an easy way to migrate from Lime Inbox to this integration. Unfortunately, the systems are too different to just migrate the data. Instead, we need to continue ongoing conversations with Inbox and create new conversations with the standard import task. This means that both integrations will run in parallel until all Inbox conversations are completed. At that point the Inbox package and this custom task can be removed. Talk with your customer about the average time of their conversations to estimate when the migration can be completed.

Application Configuration

Some as in the earlier example, the following parameter is added:

incoming_message_task: solution_your_favorite_inbox_customer.tasks.tasks.incoming

Task Code

solution_your_favorite_inbox_customer.tasks.tasks.py

import logging

from lime_application import LimeApplication
from lime_task import task
from limepkg_email.incoming_message import import_message
from limepkg_ms_inbox.limepkg_email.process import (
    check_if_message_is_for_inbox,
    process_inbox_message,
)

from solution_your_favorite_inbox_customer.endpoints.lime_inbox import _handle_email_message

logger = logging.getLogger(__name__)


@task
def incoming(app: LimeApplication, message: dict, account_id: str) -> None:
    """
    Handles incoming email for transition period of Lime inbox/Communication flow ->
    Email integration. Checks if MS Inbox or Email engine should handle it and
    sends it to the correct function.
    Args:
        app (LimeApplication: The LimeApplication instance
        message (dict): Incoming data from webhook
        account_id (str): ID of the account
    """
    subject = message["subject"]
    message_id = message["messageId"]
    logger.info(f"[handle_incoming_email.incoming] Message id [{message_id}]")
    if check_if_message_is_for_inbox(app, subject):
        logger.info(f"Message id [{message_id}] was identified as a Lime Inbox message")
        subscription_email = message["to"][0]["address"]
        process_inbox_message(
            app, subscription_email, message_id, _handle_email_message
        )
    else:
        import_message(
            app,
            message_payload=message,
            account_id=account_id,
        )

Example: Custom Message Processor

In some customer cases the standard import processor will need tweaks. You can archive that by implementing your version of the limepkg_email.incoming_message.standard.StandardIncomingMessageProcessor. In the example below everything is the same as the standard processor except that auto replies won't be sent out to lime.tech email addresses. Notice how you only need to override the method with your custom logic. Check out the limepkg_email.incoming_message.standard.StandardIncomingMessageProcessor for more methods to override.

Rules for Implementations

These rules are necessary in order to allow future updates to the standard processor without breaking your custom implementation. If you break them there's no guarantee that your custom implementation will work with future versions.

  • Don't import other functions or classes used by the standard processor. These might change or move in the future.
  • Fulfil the function's promise, e.g. a create_conversation function should create a conversation and return the limeobject. It's in your responsibility to add any objects you create to the unit of work. You may use the self.uow property on the processor class for that.
  • We recommend that you stay as close to the standard processor as possible. Completely overwriting a function without calling super() is done on your own risk and might lead to something important missing on the imported conversation. Test your process changes thoroughly.
  • You may read the following properties on the processor class: self.message (details on the incoming message), self.account (details on the account the message was sent to), self.config (customer's runtime config), self.uow, self.app.

Application Configuration

incoming_message_processor: solution_pizza.tasks.tasks.incoming

Task Code

import logging
from lime_application import LimeApplication
from lime_task import task

from limepkg_email.incoming_message import import_message
from limepkg_email.incoming_message.standard import (
    StandardIncomingMessageProcessor,
)


logger = logging.getLogger(__name__)


class CustomIncomingMessageProcessor(StandardIncomingMessageProcessor):
    """
    Follows the StandardIncomingMessageProcessor, but doesn't send automatic replies
    if message was send from the `lime.tech` domain.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def send_automatic_reply(self):
        if self.message.sender.address.endswith("lime.tech"):
            logger.info(
                f"Message '{self.message.message_id}' is from the "
                "lime.tech domain, skipping automatic reply."
            )
            return

        super().send_automatic_reply()


@task
def incoming(app: LimeApplication, message: dict, account_id: str):
    """
    Processes incoming messages using the CustomIncomingMessageProcessor.
    Blacklists automatic replies for messages from the `lime.tech` domain.
    """
    import_message(app, message, account_id, CustomIncomingMessageProcessor)