Skip to content

Configuration

Attention

If you have configured MS-inbox to create helpdesk limeobjects, please be aware. Since we are adding configuration in custom endpoint shared with MS-inbox and decorators to Custom Limeobject Helpdesk you will not be able to create helpdesk limobjects from email until both MS-inbox and Communication-flow is fully configured.

Create a custom endpoint for handling the incoming emails

  1. Follow the instructions of MS Inbox Configuration for adding a custom endpoint for handling incoming emails.

  2. Add handling for communication to the same handle function created above.

# TODO: Add this import at the top of the file
import limepkg_communication_flow.ms_inbox.behaviours as comm_behaviours

# This is the function from above MS Inbox step
def _handle_email_message(app: LimeApplication, email_data: dict) -> dict:
    # Code for processing email in MS Inbox....

    # TODO: Just before committing the unit of work add this row.
    # The helpdesk input variable will most likely come from the
    # process_email() function call, like this:
    # helpdesk = inbox_behaviours.process_email(app, email_item, uow)
    # defined in the inbox configuration examples
    comm_behaviours.parse_email_as_communication(helpdesk, uow, email_item)

    return uow.commit()

Custom Limeobject Helpdesk

There's some code that should be run for the helpdesk's custom limeobject for creating follower and such. To do so you need to add a decorator to your custom limeobject for helpdesk.

import logging

# TODO: Add decorator import
import limepkg_communication_flow.decorators as communication_flow_decorators
from lime_type.limeobjects import LimeObject

logger = logging.getLogger(__name__)


# TODO: Add this row before your custom limeobject class
@communication_flow_decorators.dress_helpdesk()
class Helpdesk(LimeObject):
    # This may contain functions like before_update, after_update and so on
    pass


def register_limeobject_classes(register_class):
    # TODO: May need to change "helpdesk" according to your table name
    register_class("helpdesk", Helpdesk)

(Optional) Auto reply with communication

Attention

If you already have configured auto reply for MS-inbox please make sure to remove the Auto reply configuration in Lime Inbox before adding this to avoid duplicate auto replies.

Info

send_system_message is using functions from MS-inbox. From email is based on the standard behaviour functions for MS Inbox. Available merge codes and subject will be as documented here MS-Inbox - Configure auto reply.

  1. To send and log an auto reply add the following code in your Custom Limeobject Helpdesk.

    Bad practice

    Adding a new unit of work in after_update in custom limeobject is considered a bad practice and should be avoided.

    This is an old implementation, and will be replaced by the new Email integration

import logging

import limepkg_communication_flow.decorators as communication_flow_decorators
# TODO: Add new traml imports
import limepkg_communication_flow.traml as comm_traml
import limepkg_transactional_message_library.traml as traml_lib
from lime_type.limeobjects import LimeObject
from limepkg_dynamic_checklist import checklist_owner

logger = logging.getLogger(__name__)

# This is a part from the Custom Limeobject Helpdesk from the step above
# in the after_update add the call to _send_traml_message
# if you are missing after_update in your 'Custom Limeobject Helpdesk' copy entire after_update
# only add the "_send_traml_message call" at the end of your after_update
@communication_flow_decorators.dress_helpdesk()
class Helpdesk(LimeObject):
    def after_update(self, unsaved_self, **kwargs):
        super().after_update(unsaved_self, **kwargs)

        # TODO: Add this call in the end of after_update
        _send_traml_message(self, unsaved_self)


def _send_traml_message(helpdesk: LimeObject, unsaved_helpdesk: LimeObject):
    if not unsaved_helpdesk.is_new:
        return

    app = helpdesk.application
    traml = traml_lib.TramlClient(app)

    # Step 1: Fetch template
    try:
        # TODO: Change to the template the customer will use
        traml_template = traml.get_mail_template_by_name(
            "limepkg-communication-flow-autoreply-template"
        )
    except traml_lib.models.TramlError as e:
        logger.warning(f"Failed to fetch template due to {e}")
        return

    try:
        # TODO: Change to whatever default email should be used
        message_meta = comm_traml.get_system_message_meta(
            helpdesk,
            "[email protected]",
        )

        # TODO: add any potential customer specific merge_codes
        merge_codes = {
            **message_meta.merge_codes,
            # Optional to add custom merge_codes
            "$$my.custom.merge_code$$": "This is just for fun",
        }
        recipients = message_meta.all_recipients
        if not recipients:
            return

        # Step 2: Create communication for sent autoreply
        uow = app.unit_of_work()
        idx_comm = comm_traml.create_system_communication(
            helpdesk=helpdesk,
            traml_template=traml_template,
            recipients=recipients,
            from_email=message_meta.from_email,
            merge_codes=merge_codes,
            uow=uow,
        )
        result = uow.commit()
        communication: LimeObject = result.get(idx_comm)

        # Step 3: Send traml email
        for recipient in recipients:
            email_model = traml_lib.models.Email(
                external_id=recipient.get_external_id(communication.id),
                # TODO: Change to whatever the customer wants
                from_name="Lime Helpdesk",
                from_email=message_meta.from_email,
                recipient_name=recipient.name,
                recipient_email=recipient.email,
                subject=message_meta.subject,
                merge_codes=merge_codes,
                exclude_totaloptouts=True,
            )
            email_model.set_autoreply_headers()
            if recipient.internet_message_id:
                email_model.set_in_reply_to_headers(
                    recipient.internet_message_id
                )

            traml.send_transactionmail_by_template(
                template_id=traml_template.id,
                message=email_model,
            )

    except traml_lib.models.TramlError as e:
        logger.warning(
            f"Failed to send autoreply for: {message_meta.subject} due to: {e}"
        )
        # TODO: Handle TramlError
        # (Remove if you want it to fail on error)
        raise


def register_limeobject_classes(register_class):
    register_class("helpdesk", Helpdesk)

Lime Admin

Add-on config

Go To: Add-ons => Communication flow

The only things you have to configure is which limetypes it's supposed to use.

Here is an example of a standard setup:

image info

  • Set communication automatically as read, when entering communication-feed? => If True, helpdesk will automatically be marked as read.
  • Property where datetime is saved for latest unread communication => datetime field where datetime for latest unread communication is saved. For visualization in table view update the table view as described here.

image info

image info

image info

Info

By default all files from communication flow are copied to document on helpdesk. You must set the optionkey, see below. However you can disable this and communication flow files will only be accessible in communication flow.

  • Optionkey for documents created from sys_communicationdocument => The optionkey for documents created from sys_communicationdocument.
  • Disable copy document => Set to True if you don't want documents from communication flow to be copied to documents.

Views config

Info

The limeobjects sys_communication, sys_follower, sys_communicationdocument and sys_communicationfollower are intended to be hidden in your solution, meaning that no views should be configured for these.

Communication Flow

To be able to see the communication flow in the web client you need to add it as a custom tab on the relevant limetype i.e. helpdesk.

Go To: Views => Relevant Limetype

Scroll down to Tabs and add the following:

  • Title => limepkg_communication_flow.communication.feed.tab.title
  • Is translation key => True
  • Icon => message
  • Color => <empty> (Nothing)
  • Web component - Name => lwc-limepkg-communication-flow-root
  • Web component - Properties => {}

image info

Mark as read/unread communication

To add a button on helpdesk where the user can mark a communication as read or unread.

Go To: Views => Relevant Limetype => Card

Scroll down to Web components and add the following:

  • Name => lwc-limepkg-communication-flow-unread-communication-card-view
  • Properties => {}

image info

Visualize unread communication

Unread communication is by default a datetime field and to visualize unread/new communications in the table view we need to add a web-component in the table view.

Go To: Views => Helpdesk limetype => Table

Find the datetime field used to save unread communications and add the following to Component:

  • Name => lwc-limepkg-communication-flow-unread-communication-table-view
  • Properties => {}

image info

Communication flow template

Go To: Views => Communication flow template

Info

Please note the web-component, lwc-limepkg-communication-flow-template-config, on field template. Template is visualized and editable through an editor to support formatting. Data is saved as html.

Paste the following json.

{
    "general": {
        "primaryTitle": [
            {
                "property": "name"
            }
        ],
        "icon": "template",
        "color": "rgb(var(--color-blue-default))",
        "create": true,
        "inlineCreate": false,
        "bulkDeleteObjects": true,
        "views": [
            {
                "view": {
                    "props": {},
                    "name": "limec-table-view"
                },
                "title": "Table",
                "icon": "insert_table"
            }
        ],
        "globalTablesMenu": true
    },
    "card": {
        "sections": [
            {
                "collapsed": true,
                "layout": {
                    "columns": 5,
                    "dense": true
                },
                "controls": [
                    {
                        "layout": {
                            "colSpan": 4
                        },
                        "component": {
                            "props": {}
                        },
                        "property": "name",
                        "required": true
                    },
                    {
                        "layout": {},
                        "component": {
                            "props": {}
                        },
                        "property": "inactive"
                    }
                ],
                "title": "untitled"
            },
            {
                "collapsed": false,
                "layout": {
                    "columns": 5,
                    "dense": true
                },
                "controls": [
                    {
                        "layout": {
                            "colSpan": 5
                        },
                        "component": {
                            "props": {},
                            "name": "lwc-limepkg-communication-flow-template-config"
                        },
                        "property": "template"
                    }
                ],
                "title": "Template"
            }
        ]
    },
    "list": {
        "header": [
            {
                "property": "name"
            }
        ]
    },
    "search": {
        "header": [
            {
                "property": "name"
            }
        ]
    },
    "table": {
        "columns": [
            {
                "isDefault": true,
                "component": {
                    "props": {}
                },
                "property": "name"
            },
            {
                "isDefault": true,
                "component": {
                    "props": {}
                },
                "property": "inactive"
            }
        ],
        "actions": []
    }
}

Text Template Configuration

Go To: Add-ons => Communication flow => Text Template Configuration

Text template configuration

  • Use text templates => Set to True to enable text templates in Communication flow editor.

Info

Even if Use text templates is True at least one template must be active in order for the template picker to be visible in Communication flow editor.

If you would like to group the templates add an Option or Relation (field) field on limetype sys_communicationtemplate. Don't forget to add it to the webclient card view as well. For option field text will be used as group name and for relation field descriptive will be used as group name. Name and group name will be searchable when using text templates in the composer.

  • Group by => Select the field you have created in sys_communicationtemplate.

Setting policies on templates

It is possible to use policies on the text templates, both on table and field. So you can easily set the appropriate policies on both the field and editor.

Create filter to highlight tickets with undelivered email

Sometimes outgoing email are not delivered due to different reasons, where a bounce is the most common culprit. To help users understand which tickets have messages that have bounced create the following filter on the Helpdesk table by opening the advanced filter editor and copy paste the JSON below. Save the filter as Tickets with undelivered emails past 7 days. It is also recommended to create an Infotile connected to this filter.

Tickets with undelivered emails past 7 days

{
    "op": "AND",
    "exp": [
        {
            "key": "sys_communication.sys_communicationfollower.sys_communication.sys_communicationfollower.traml_delivery_description",
            "op": "!",
            "exp": ""
        },
        {
            "op": "AND",
            "exp": [
                {
                    "key": "sys_communication.sys_communicationfollower.traml_send_date",
                    "op": ">=",
                    "exp": "$previous_day(7)"
                },
                {
                    "key": "sys_communication.sys_communicationfollower.traml_send_date",
                    "op": "<=",
                    "exp": "$today"
                }
            ]
        }
    ]
}

Application Config

Froala

The editor in Communication flow is using the third-party FROALA WYSIWYG Editor. So you need to add the Lime internal Froala license key to application_config.yaml (or application config in CAFE).

Locally the application config will have the following structure:

solution-x:
    secrets:
        froala:
            license_key: <froala-license-key>

Froala License Key

froala-license-key can be found here

Inline image handling

Communication flow will try to avoid saving images as documents that are smaller in size (filesize). For example small thumbnails in email signatures. They will still be stored and rendered in the message, just not considered an attachment in other contexts

By default that size limit is set to 25KB, but can be changed in the application config like this:

solution-x:
    config:
        limepkg_communication_flow:
            inline_image_size_limit: 25600 # Size in Bytes (25 * 1024B) => 25KB

If in cloud

Exclude the solution-x part and only add config:...

Configure TRAML for sending the e-mails

If you use the standard behaviour functions for MS Inbox. We will store the subscription e-mail on the helpdesk in a hidden field that can be used as "sender" when sending the e-mail. That way you can have multiple inboxes setup and the correct e-mail address will be used for each communication sent.

All sendouts are done in the customer's own solution, but we have some help functions/classes that you can use for setting it up easily. We use TRAML, so you need to setup a Lime Marketing account for the customer for this to be used. This is normally already setup for the Auto-responses in Lime Inbox.

Add webhook subscriptions in Lime Marketing

This so that we in the communication flow can see if an email bounced or is sent/delivered without issue.

Go to Integrations>Webhook subscriptions, and create a new webhook subscription with the following information:

  • Name: Lime CRM Communication flow
  • URL: https://<server>/<limeenvironment>/limepkg-communication-flow/traml/webhook/
  • AppId: <Leave empty>
  • Secret: <Leave empty>
  • Send batched webhooks: OFF
  • Headers: x-api-key: <API-key for the lime crm communication-flow user, that you have to create and generate an API-key for in LISA>
  • Subscribe to all webhooks: Chose specific
  • Turn on webhooks for:

    • transactionmail.delivered
    • transactionmail.bounced
    • transactionmail.sent
    • transactionmail.not_sent

    Warning

    Spaces matter! Don't remove the one after x-api-key:

Custom Limeobject for sys_communication

Create a custom limeobject for sys_communication and add the logic in after_update. This is just an example and you may change it to whatever you'd like.

There's also some code that should be run for the communication's custom limeobject to automatically update the related helpdesk. To do so you need to add a decorator to your custom limeobject for communication.

Recommended!

Want to create a history note for every incoming and outgoing communication see (Optional) Create a summarizing history note when sending and receiving emails.

import logging
# TODO: Add typing imports
from typing import Optional

# TODO: Add traml and decorator imports
import limepkg_communication_flow.decorators as communication_flow_decorators
import limepkg_communication_flow.traml as comm_traml
import limepkg_transactional_message_library.traml as traml_lib
from lime_type.limeobjects import LimeObject

logger = logging.getLogger(__name__)


@communication_flow_decorators.sys_communication()
class Communication(LimeObject):
    _message_meta: Optional[comm_traml.CommunicationMeta] = None

    @property
    def message_meta(self) -> comm_traml.CommunicationMeta:
        if not self._message_meta:
            # The second parameter is a default fallback
            # if the helpdesk wouldn't contain a subscription_email.
            # an example of that would be if a user in
            # lime created a helpdesk manually in the client
            # TODO: Change Default E-mail to
            # the customers default helpdesk inbox
            self._message_meta = comm_traml.get_communication_message(
                self, "[email protected]"
            )
        return self._message_meta

    def after_update(self, unsaved_self, **kwargs):
        super().after_update(unsaved_self, **kwargs)

        _send_traml_message(self, unsaved_self)


def _send_traml_message(
    communication: Communication,
    unsaved_communication: Communication,
):
    if not unsaved_communication.is_new:
        # Only send e-mail if communication is new
        return
    elif communication.properties.source.value.key != "lime":
        # Only send e-mail if communication is created from communication feed
        return
    elif communication.properties.system_message.value:
        # if auto reply(system_message) we have already sent an email
        # with system_message (helpdesk custom limeobject)
        return

    try:
        traml = traml_lib.TramlClient(app=communication.application)

        # TODO: Change to whatever the customer wants
        from_name = "Lime Helpdesk"

        # TODO: Change to the template the customer will use
        template = traml.get_mail_template_by_name(
            "limepkg-communication-flow-communication-template"
        )
    except traml_lib.models.TramlError as e:
        logger.warning(f"Failed to fetch template due to {e}")
        return

    message_meta = communication.message_meta

    for recipient in message_meta.all_recipients:
        try:
            message = traml_lib.models.Email(
                external_id=recipient.get_external_id(communication.id),
                from_name=from_name,
                from_email=message_meta.from_email,
                recipient_name=recipient.name,
                recipient_email=recipient.email,
                subject=message_meta.subject,
                attachments=[
                    traml_lib.models.Attachment.from_lime_file(a.file)
                    for a in message_meta.attachments
                ],
                merge_codes=message_meta.merge_codes,
                exclude_totaloptouts=True,
            )

            if recipient.internet_message_id:
                message.set_in_reply_to_headers(recipient.internet_message_id)

            traml.send_transactionmail_by_template(
                template_id=template.id,
                message=message,
            )
        except traml_lib.models.TramlError as e:
            logger.warning(
                "Failed to send e-mail for sys_communication "
                f"with ID: {communication.id} due to {e}"
            )


def register_limeobject_classes(register_class):
    register_class("sys_communication", Communication)

The subject will be set to the helpdesk title and then the helpdesk number inside square brackets for example: My title of helpdesk [abc123]

There are some default merge_codes that will be added automatically:

Name Description
$$helpdesknumber$$ The helpdesk number of the related helpdesk
$$sender.name$$ The name of the follower writing the communication message
(will fallback to the e-mail if no name is given)
$$sender.email$$ The email of the follower writing the communication message
$$communication.body$$ The body of the communication message that trigger the sendout
$$answer_above_prefix$$ The prefix tag for answer above this line. Imported from ms-inbox
$$answer_above_suffix$$ The suffix tag for answer above this line. Imported from ms-inbox

(Optional) Add previous communications to the TRAML email

It is possible to fetch previous communications and add them as a merge code and send to TRAML.

See below for an example:

Warning

This example customization includes ALL earlier communication, meaning that it will always include email history, even new followers in the conversation will be able to read earlier emails from all followers.

If you want to exclude some communications, for example internal ones you have to make your own adjustments.

Info

The code below expects you to have at least version v3.9.0 of Communication flow

If needed it can be modified to work for older versions, but we always recommend to upgrade if possible.

Do the following steps in your sys_communication custom limeobject file

1. Add these imports

import datetime
from typing import Dict, List, Optional

import babel.dates
import lime_errors

import limepkg_communication_flow.communication_items as communication_items
from lime_type import OptionProperty

2. Add these functions

def _fetch_previous_communications(communication_obj: Communication) -> str:
    # TODO: Change (Or empty) to customer name
    customer_company_name = "The Customer Name"

    # Make sure that the relation property here is called "helpdesk"
    app: LimeApplication = communication_obj.application
    helpdesk: LimeObject = communication_obj.properties.helpdesk.fetch()
    if not helpdesk:
        return ""

    communication_data = communication_items.fetch_communications(
        app=app,
        owner_id=helpdesk.id,
        owner_limetype=helpdesk.limetype.name,
        skip_attachments=True,
        skip_recipients=True,
    )

    grouped_followers: Dict[str, dict] = {
        follower["follower_id"]: follower
        for follower in communication_data.get("followers") or []
    }

    communication_bodies: List[str] = []
    for communication in communication_data.get("communications") or []:
        if communication["_id"] == communication_obj.id:
            continue  # Skip current communication

        sent_date: Optional[datetime.datetime] = communication[
            "communication_timestamp"
        ]

        helpdesk_history = communication["body"]

        sender_title = (
            _format_sender(
                app=app,
                sender=grouped_followers.get(communication["sys_follower"]),
                company_name=customer_company_name,
            )
            or "-"
        )

        # Add sender information
        communication_content = (
            '<p style="'
            "    font-family: 'Lucida Grande', 'Lucida Sans Unicode',"
            "        'Lucida Sans', Verdana, Tahoma, sans-serif;"
            "    font-size: small;"
            "    margin-bottom: 0px;"
            "    margin-top: 0px;"
            "    padding: 0px;"
            "    opacity: 0.8;"
            '">'
            f"{sender_title}"
            "</p>"
        )

        # Add sent date
        communication_content += (
            '<p style="'
            "    font-family: 'Lucida Grande', 'Lucida Sans Unicode',"
            "        'Lucida Sans', Verdana, Tahoma, sans-serif;"
            "    font-size: smaller;"
            "    margin-bottom: 15px;"
            "    margin-top: 0;"
            "    padding: 0;"
            "    opacity: 0.7;"
            f'">{_format_datetime(app.language, sent_date) or "-"}</p>'
        )

        communication_content += f"<div>{helpdesk_history}</div>"

        communication_bodies.append(communication_content)

    helpdesk_history = '<p style="border-top: 1px dotted rgb(197, 197, 197)"/>'.join(
        communication_bodies
    )

    return (
        '<div style="width: 100%; margin: 5px">'
        '<p style="border-top: 1px dotted rgb(197, 197, 197)"/>'
        f"{helpdesk_history}"
        "</div>"
    )


def _format_sender(
    app: LimeApplication,
    sender: dict,
    company_name: Optional[str] = None,
) -> Optional[str]:
    if not sender:
        return

    sender_title = ""
    sender_subtitle = ""

    sender_name = sender.get("name")
    sender_email = sender.get("email")
    role = sender.get("role")
    if role == "autoreply":
        # If auto-reply => Use Option text
        sender_subtitle = company_name
        role_prop: OptionProperty = app.limetypes.sys_follower.properties.role
        try:
            sender_title = role_prop.get_by_key(role).text
        except lime_errors.NotFoundError:
            sender_title = role
    elif role == "agent":
        # If from Agent => Skip e-mail AND Add company name
        sender_title = sender_name
        sender_subtitle = company_name
    else:
        # Else add name and email
        if sender_name == sender_email:
            sender_title = sender_name
        else:
            sender_title = f"{sender_name} &lt;{sender_email}&gt;"

    if not sender_title:
        return ""

    parts = [f"<strong>{sender_title}</strong>"]
    if sender_subtitle:
        parts.append(f"<span>({sender_subtitle})</span>")

    return " ".join(parts)


def _format_datetime(lang: str, date: Optional[datetime.datetime]) -> str:
    if not isinstance(date, datetime.datetime):
        return ""

    try:
        return babel.dates.format_datetime(date, locale=lang)
    except Exception:
        return babel.dates.format_datetime(date, locale="en")

3. Add the this merge code to your other merge codes before your looping through recipients

merge_codes = {
    **message_meta.merge_codes,
    "$$helpdesk.history$$": _fetch_previous_communications(communication),
}
# for recipient in ... comes here

4. Pass the new merge codes to the traml mail

In the regular template you probably set the merge_codes in the traml mail like this:

merge_codes=message_meta.merge_codes

Now you should change so that it passes the merge_codes from step 3 like this:

merge_codes=merge_codes,

5. Go through TODO:s

Make sure to search for TODO: in the file and follow each instruction. After your done, remove the comment.

6. Add new merge code to your TRAML template

You have a template in TRAML that you use for the communication TRAML email in Lime Marketing.

Make sure to add the merge code $$helpdesk.history$$ at the end of the template.

Result

Example of conversation

Info

Communication Flow and the feed is intended to be the place for all email communications. That means that the history feed on a ticket should not have the messages. Instead, the history notes should be the internal log on the ticket – where a user will manually log an actual comment on the ticket – stating more concise and specific on what has been said, done and promised. If a user wants to dig deeper, they can always see the complete conversation in the Communication Flow tab – but history should not be littered with long emails. At the same time the history feed comes with technical limitations, it can’t display the formatting and potential pictures in the emails.

This custom lime object extends sys_communication documented above. It will create a history note when receiving or sending an email through the communication flow. It will highlight who sent and to whom, connecting all relations of the helpdesk object to the history object. Please note that this custom lime object uses limepkg-base-solution-helpers to handle the relation attachments, thus it needs to be added to your solution by running poetry add limepkg-base-solution-helpers. Also please note that this custom lime object also contains the configuration done at the previous step Custom Limeobject for sys_communication

1. Add these imports

import limepkg_base_solution_helpers.common as base_helpers

2. Add this function

def _create_history(
    communication: Communication,
    unsaved_communication: Communication,
):
    if not unsaved_communication.is_new:
        return

    message_meta = communication.message_meta
    if not message_meta.from_follower:
        return
    try:
        from_limeobject = message_meta.from_follower.follower_limeobject
    except lime_errors.NotFoundError:
        from_limeobject = None

    app: LimeApplication = communication.application
    uow = app.unit_of_work()

    helpdesk = communication.properties.helpdesk.fetch()

    com_source = communication.properties.source.value.key

    history: LimeObject = app.limetypes.history()
    uow.add(history)

    base_helpers.relate_all_relations(
        helpdesk,
        history,
        uow,
        exclude_relations=[
            "person",
            "coworker",
        ],
    )

    if com_source == "lime":
        recipient_names = ", ".join([r.name for r in message_meta.all_recipients])

        note = (
            f"**{message_meta.from_follower.name}** "
            f"sent an email to **{recipient_names}**"
        )

        type_key = "sentemail"
        if from_limeobject:
            uow.add(from_limeobject)
            history.properties.coworker.attach(from_limeobject)

        person = helpdesk.properties.person.fetch()
        person_email = person.properties.email.value if person else None
        is_person_recipient = person_email and any(
            (
                recipient.email == person_email
                for recipient in message_meta.customer_recipients
            )
        )
        if is_person_recipient:
            uow.add(person)
            history.properties.person.attach(person)
    elif com_source == "email":
        type_key = "receivedemail"
        note = f"Received email from **{message_meta.from_follower.name}**"

        coworker = helpdesk.properties.coworker.fetch()
        if coworker:
            uow.add(coworker)
            history.properties.coworker.attach(coworker)

        if from_limeobject and from_limeobject.limetype.name == "person":
            uow.add(from_limeobject)
            history.properties.person.attach(from_limeobject)
    elif com_source == "autoreply":
        recipient_names = ", ".join([r.name for r in message_meta.all_recipients])

        note = f"Autoreply sent to **{recipient_names}**"

        type_key = "sentemail"
        if history.properties.coworker.value:
            history.properties.coworker.detach()

    history.properties.type.set_by_key(type_key)
    history.properties.note.value = note
    history.properties.helpdesk.attach(helpdesk)
    uow.add(helpdesk)

    return uow.commit()

3. Add function call to above function in after_update

Bad practice

Adding a new unit of work in after_update in custom limeobject is considered a bad practice and should be avoided.

This is an old implementation, and will be replaced by the new Email integration

class Communication(LimeObject):
    def after_update(self, unsaved_self, **kwargs):
        super().after_update(unsaved_self, **kwargs)

        _create_history(self, unsaved_self) # TODO: Add this row
        _send_traml_message(self, unsaved_self)

Images of the history notes

See attached screenshot of it looks on a company and a helpdesk ticket image