Skip to content

Quick configuration guide

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

This section contains a quick guide for setting up comminucation flow with lime inbox

For those who feels comfortable with code, please go to the regular configuration

This is a copy/paste of each file you'll need so you don't have to take any decisions.

Just make sure to update everywhere where it says: # TODO:

Warning

If you have MS inbox from before make sure that you don't have any customization or anything other than the default behaviour before you copy paste these examples.

This also includes custom limeobject for helpdesk and/or sys_communication

1. Lime Admin

Follow the instructions under this section and then come back here

2. Application config

Follow the instructions under this section and then come back here

3. 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 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.bounced
  • transactionmail.sent
  • transactionmail.not_sent
  • transactionmail.delivered

    Warning

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

Simple auto reply template

Please note that top $$answer_above_prefix$$ and $$answer_above_suffix$$ should always be a part of all communication flow TRAML-templates as explained here, but it's ok to write whatever you want in between them. It is important that these merge codes are at the very top of the template.

Simple auto reply template

$$answer_above_prefix$$ Answer above this line $$answer_above_suffix$$

Hello,

We have created a support ticket, $$helpdesknumber$$.

If you want to communicate further regarding this issue, please reply to this email keeping the subject intact.

Best regards,

Lime Technologies

Screenshot of template in Lime Marketing

simple_autoreply

Simple communication template

This is the template that send the message you write manually in the editor. The merge code &&sender.name&& is used in the bottom to fetch the users name. This means that they don't have to sign each email with their name. If they want to modify their signature that can be done by e.g. adding a signature field on the coworker card and fetch that instead.

Please note that top $$answer_above_prefix$$ and $$answer_above_suffix$$ should always be a part of all communication flow TRAML-templates as explained here, but it's ok to write whatever you want in between them. It is important that these merge codes are at the very top of the template.

Simple communication template

$$answer_above_prefix$$ Answer above this line $$answer_above_suffix$$

$$communication.body$$

Best regards,

$$sender.name$$

Screenshot of example template in Lime Marketing

simple_communication

4. Limeobject for helpdesk (limeobject_classes/helpdesk.py)

import logging

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.dress_helpdesk()
class Helpdesk(LimeObject):
    def before_update(self, uow, **kwargs):
        """
        This is called on all new and updated objects. All changes
        made to the object here will be persisted.
        All other objects that are changed or created here must be
        added to the unit of work.
        """
        super().before_update(uow, **kwargs)

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

        _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]",
        )

        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=message_meta.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=message_meta.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)

5. Limeobject for sys_communication (limeobject_classes/sys_communication.py)

import logging

import limepkg_base_solution_helpers.common as base_helpers
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_application import LimeApplication
from lime_type.limeobjects import LimeObject
from typing import Optional
import lime_errors


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)

        _create_history(self, unsaved_self)
        _send_traml_message(self, unsaved_self)


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()


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)

6. Create a custom endpoint for handling the incoming emails endpoint_inbox.py (endpoints/endpoint_inbox.py)

import limepkg_communication_flow.ms_inbox.behaviours as comm_behaviours
import limepkg_ms_inbox.behaviours as inbox_behaviours
from lime_application.application import LimeApplication
from limepkg_ms_inbox.ms.api.resource import (
    NotificationResource,
    RecoveryResource,
    with_notification,
    with_recovery,
)

from . import api


class Notification(NotificationResource):
    @with_notification
    def post(self, app, email_data):
        _handle_email_message(app, email_data)


api.add_resource(Notification, "/inbox/")


class Recovery(RecoveryResource):
    @with_recovery
    def post(self, app, email_data):
        _handle_email_message(app, email_data)


api.add_resource(Recovery, "/inbox/recover/")


def _handle_email_message(app: LimeApplication, email_data: dict) -> dict:
    uow = app.unit_of_work()
    email_item = inbox_behaviours.EmailItem(email_data)
    if inbox_behaviours.have_email_been_processed(app, email_item):
        # If the incoming e-mail is being processed or has been processed
        return False
    elif email_item.is_autoreply:
        # If the incoming e-mail is an auto-reply
        return
    helpdesk = inbox_behaviours.process_email(
        app=app,
        email_item=email_item,
        uow=uow,
        attachments_behaviour=(
            inbox_behaviours.AttachmentBehaviour.NO_ATTACHMENTS
        ),
        history_behaviour=inbox_behaviours.HistoryBehaviour.NO_HISTORY,
    )
    comm_behaviours.parse_email_as_communication(helpdesk, uow, email_item)
    return uow.commit()

7. Add the resource (subscription to an email mailbox) in Lime Admin

Go to Lime Admin => (Add-ons OR Settings) => Lime Microsoft Inbox => Your valid client

In the bottom of the second section you'll see an Add button, click it and fill in the form:

Email is the email-address that you want to monitor and it should be accessible by your Azure Enterprise Application if it was setup correctly.

Webhook URL is the endpoint where you will recieve your notifications.

https://<server-address>/<application name>/<solution-name>/inbox/

Recovery URL [Optional] is the endpoint that will be called when using the recovery functionality.

https://<server-address>/<application name>/<solution-name>/inbox/recover/

Inactive Ticking this box will make lime-inbox ignore all incoming emails. They will instead show up as unprocessed later.

8. (Optional) Check out customizations that might suit your customer