Skip to content

Configuration

Azure Enterprise Application setup

Lime Inbox requires an Azure application to function. An Azure application is refered to as a client below. Basically a client is used to communicate with the Microsoft Graph API and will provide us with client_id, tenant_id, and client_secret. We run this as a daemon application so there is no user interactions.

Warning

Steps 1-3 of the configuration needs to be done by the customer Admin in Azure.

To configure this we need to complete these steps in the Azure portal portal.azure.com:

1. Create and register an application [Customer]

Info

We suggest to create a separate dedicated application but you can use an existing.

Follow the guide to register an app: Link to guide to register an app

  1. Give it a unique name
  2. For Supported Accounts Types, it depends on the setup. If it's managed from a directory (single tenant), use "Accounts in this organizational directory only"
    • Usually we use the Single tenant option
  3. For Redirect URI
    • Select Web
    • Leave Uri Empty

2. Add client secret [Customer]

Info

This secret will have an expiration date, this is up to the customer to keep track of. Inbox will stop working completely when the expiration runs out.

Now follow this step to create a client secret: Link to guide to add a client secret

When created you will see the client secrets. We only need the Value and not Secret ID

Warning

IMPORTANT! Copy the client secret value and store it somewhere safe! Lime will need these at later step.

If you refresh the page it is lost forever!

3. Setup permissions (Add API Permissions) [Customer]

Follow this to setup permissions: Link to guide to setup permissions

  • These permissions should be added as Application permissions
    • Microsoft Graph -> Application Permissions -> Mail.Read
    • Microsoft Graph -> Application Permissions -> Mail.ReadBasic
    • Microsoft Graph -> Application Permissions -> Mail.ReadWrite
  • Grant admin consent to these permissions

Warning

IMPORTANT! Someone needs to accept these permissions if you're not an Global Admin yourself.

Will be marked by a yellow exclamation (image below)

image info

Permissons levels

Info

Per default Microsoft grants access to all Inboxes. If the customer wants to limit the access of the application to a specific mailbox, see Limiting application permissions to specific Exchange Online mailboxes (Microsoft Docs).

4. Public access (on-premise only) [Customer]

Customer using Self-signed certificate?

if the customer has a self signed certificate, add path_to_cert in the application config

For Lime Inbox to work, some parts of the Lime CRM server must be exposed to the internet to allow Microsoft's services to communicate with Lime. Either full public access to the server can be given, or access can be restricted to the specific endpoints that Lime Inbox use.

Exactly how to minimize the exposure is up to the customer's IT department to solve. Commonly, our customers use a combination of proxies, reverse-proxies, DMZ, and of course, firewalls.

The URL patterns that must be exposed can be found here.

5. Update Lime CRM's application config [Lime]

Update Lime CRM's application config with the credentials and secret from the customer. Go your application config and replace the following:

  • <client_id>
  • <tenant_id>
  • <client_secret>

Info

You can find the client id and tenant id from the customers application overview page.

At this point, try restarting your app server and then go to the lime admin section again. Clicking validate should now succeed if you have correctly setup your Azure Enterprise application.

If it doesn't work, check out Troubleshooting for common issues and how to solve them.

Application Config

The application_config.yaml file (or config tab in CAFE) contains some settings for inbox but mainly the available clients.

Info

Lime Inbox support the use of several Azure Applications in one Lime Application. This is done by adding several <client-names> with corrensponding configurations and secrets in your application_config.

Customer using Self-signed certificate?

if the customer has a self signed certificate, add path_to_cert in the application config

Configuring clients

A client in this context is a connection to the Microsoft Graph API which is needed to connect and authenticate.

Where client-id, tenant-id and client-secret are used to authenticate to a specific Enterprise Application in Azure. These values are created and given to the consultant by the customer (See azure enterprise application setup on how to set this up)

However, you should be able to verify that a client shows up in Lime Admin if you add the config above in the application_config.yaml file. Replace <my-application> with your solution name, and put whatever you want for client_id, tenant_id and client_secret. (<my-client-name> is the name of your client and can be set to whatever you want)

If you go to Lime Admin after this your should see something similar to this:

image info

You can click Validate it checks that you have provided valid ids and secret. If something is wrong you should recieve an error message in the bottom of this page.

To fix this we need correct values for client_id, tenant_id and client_secret.

Inbox settings

Usually you don't have to set any of these, but can sometimes be needed.

Name Required Default value Description
config.limepkg_ms_inbox.recovery.path_to_verify_cert No - A full path to a cert file that will be used to verify the SSL cert when using the recovery functionality. Read more details here be able to support self-signed certificates
config.limepkg_ms_inbox.sync_history_length No 50 How many "sync & save" logs to store for each client.
config.limepkg_ms_inbox.custom_select No [] A list of additional $select properties to send to Ms Graph api, read more about it here.

path_to_verify_cert

When specifiyng the path, make sure to escape the backslashes (\) by using two backslashes for each.

like this: C:\\MyFolder\\MyCert.pem

Make sure that the Service user running the Lime services have read access to the file you specify.

If you don't you will get a error like this:

PermissionError: [Errno 13] Permission denied

Config examples

Don't forget to replace everything inside <>

Standard config

<my-application>:
    config:
        limepkg_ms_inbox:
            <my-client-name>:
                client_id: <client-id>
                tenant_id: <tenant-id>
    secrets:
        limepkg_ms_inbox:
            <my-client-name>:
                client_secret: <client-secret>

Full config with all settings

Warning

This is just an example to show the structure of the config.

The values would not work in a live environment

<my-application>:
    config:
        limepkg_ms_inbox:
            recovery:
                path_to_verify_cert: "C:\\MyFolder\\MyCert.pem"
            sync_history_length: 50
            custom_select:
                - custom_field_1
                - custom_field_2
            <my-client-name>:
                client_id: <client-id>
                tenant_id: <tenant-id>
    secrets:
        limepkg_ms_inbox:
            <my-client-name>:
                client_secret: <client-secret>

Lime Admin Configuration for resources.

After successfully validating your client you should see a new section with resources in the Lime Admin page for the addon.

1. Create custom endpoints for handling the emails

Error handling

If you have error handling in your code. Keep in mind that if you don't raise an exception the message will be deleted and not set to status Failed. So if you want to log some specific message make sure to raise the exception again after that.

See example below:

try:
    value = 1 / 0 # This will raise an exception
except ZeroDivisionError:
    logger.warning("Failed to divide by zero")
    # The row below will raise the original exception so the
    # message will be marked as failed instead of removing it in the inbox.
    raise

Are you using Communication flow aswell?

If you're not comfortable with coding there's a Configuration for dummies where you can copy/paste "everything" and can skip the rest of this documentation.

To be able to add a resource (subscription to an email mailbox) in Lime admin. You'll need a custom endpoint that handles the notifications.

It can be done by running: lime-project generate endpoint

You can use this example endpoint to do any kind of custom logic that you want:

Example with the simplest default logic

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


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


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


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


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


def _handle_email_message(app: LimeApplication, email_data: 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

    inbox_behaviours.process_email(app, email_item, uow)

    return uow.commit()

Example customizations

How to implement the customization

Use the above default behaviour and just replace the _handle_email_message function

Set helpdesktype based in inbox address
def _handle_email_message(app: LimeApplication, email_data: 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, email_item, uow)

    # Custom adjustments for your solution:
    # Set helpdesk_type based on what inbox received the email
    helpdesk_types = {
        "[email protected]": 1002,
        "[email protected]": 1337,
    }

    if email_item.subscription_email in helpdesk_types:
        helpdesk_type_id = helpdesk_types[email_item.subscription_email]
        try:
            helpdesk_type = app.limetypes.helpdesktype.get(helpdesk_type_id)
            helpdesk.properties.helpdesktype.attach(helpdesk_type)
            uow.add(helpdesk_type)
        except Exception:
            logger.warning(f"failed to set helpdesktype: {helpdesk_type_id}")

    return uow.commit()
Re-open closed helpdesk

Warning

This example is hard coded with property helpdeskstatus and keys: done parked and started

def _handle_email_message(app: LimeApplication, email_data: 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, email_item, uow)

    accessor_helpdeskstatus = helpdesk.properties.helpdeskstatus
    if helpdesk.is_new is False and accessor_helpdeskstatus.value.key in [
        "done",
        "parked",
    ]:
        accessor_helpdeskstatus.set_by_key("started")


    return uow.commit()
Create a new helpdesk when receiving e-mail on a closed helpdesk

Warning

This example is hard coded with property helpdeskstatus and keys: done

def _handle_email_message(app: LimeApplication, email_data: 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

    existing_helpdesk = inbox_behaviours.find_helpdesk_by_subject(
        app, email_item.subject
    )

    force_new_helpdesk = False
    if (
        existing_helpdesk
        and existing_helpdesk.properties.helpdeskstatus.value.key == "done"
    ):
        force_new_helpdesk = True

    helpdesk = inbox_behaviours.process_email(
        app=app,
        email_item=email_item,
        uow=uow,
        force_new=force_new_helpdesk,
    )

    return uow.commit()

Info

There are tools available as you see above to help you handle whatever logic the customer is asking for. You can find the documentation for that here

Warning

Note that you should use app and NOT self.application as LimeApplication to e.g. create a unit of work.

email_data is of type dict and contains a email message. The email-message includes a variety of properties, e.g. subject, bodyPreview, subscriptionEmail, attachments and so on.

Be sure to use the wrapper EmailItem to easier use these properties

2. 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. You will have to create this endpoint yourself in your solution, see here

Recovery URL [Optional] is the endpoint that will be called when using the recovery functionality. You will have to create this endpoint yourself in your solution, see here

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

3. [Optional] Configure auto reply

When creating for example a helpdesk a common scenario is that you want an auto reply to be sent to the customer saying that we received your e-mail and this is your ticket number.

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 "from address" when sending the e-mail. That way you can have multiple inboxes setup and the correct e-mail address will be used for each auto reply.

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 TRAML2, so you need to setup a Lime Marketing account for the customer for this to be used.

In order for Lime to send emails from a company domain, permissions needs to be granted by the domain owner. You can find instructions here.

Custom Limeobject for helpdesk

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

import logging

# TODO: Add new traml imports
import limepkg_ms_inbox.behaviours.helpdesk.traml as inbox_traml
import limepkg_transactional_message_library.traml as traml_lib
from lime_type.limeobjects import LimeObject

logger = logging.getLogger(__name__)


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(
            "lime-inbox-autoreply"
        )
    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 = inbox_traml.get_helpdesk_message(
            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",
        }

        # Step 2: Send traml email
        email_model = traml_lib.models.Email(
            # TODO: Change to whatever the customer wants
            from_name="Lime Helpdesk",
            from_email=message_meta.from_email,
            recipient_name=message_meta.recipient.name,
            recipient_email=message_meta.recipient.email,
            subject=message_meta.subject,
            merge_codes=merge_codes,
            headers=message_meta.headers,
            exclude_totaloptouts=True,
        )

        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)

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 created helpdesk

4. [Optional] Configure Status addon on start page

Messages can be stuck in status Unprocessed if for example the scheduled task isn't running and the webhook for some reason didn't send the notification. If an error occur while processing a message it will be set to status Failed and will not be imported.

You can see this by going into Lime Admin and check status for each inbox, OR you can use the Status addon to see a summary directly on the start page.

  1. Configure who can access/see the inbox summary, by going to:

    Settings => Lime Microsoft Inbox => Status Addon and choose which groups that should have access.

  2. Add it on a start page, by going to: System => Start Pages

    And set the following settings:

    • Name: lwc-limepkg-ms-inbox-status-addon
    • Size: Small rectangle

You can also set the following Properties if you want:

Name Type Default value Description
showInactive boolean false If inactive inboxes should be shown in the summary or not
showAll boolean false If the summary should show all inboxes even valid ones

This will show a summary inboxes contaning one of the below:

  • All failed messages in all inboxes
  • All unprocessed messages, that's been there more than 5 minutes in all inboxes (This so that we shouldn't see messages that's being processed or if a notification is taking some time to be sent)
  • Status of each inbox Active, Expired or Inactive (if showInactive is enabled)

If there aren't any Expired inbox or failed/unprocessd message the summary won't show at all in the start page (unless showAll is enabled)

How it looks

Unprocessed, failed and expired
Unprocessed, failed and expired
Unprocessed, failed and NO expired
Unprocessed, failed and NO expired

Summary

Summary

5. Sum up

At this point, we are almost ready to start recieving emails. Everytime the email address you subscribe to recieves an email it will be forwarded to your Webhook endpoint.

Warning

Your endpoint need to be accessible from outside the server. Because Azure will call the endpoint.

Try to send an email to the adress you specified. Your endpoint should be triggered.

Info

Note that the only mailbox folder that's monitored is the one called Inbox. By default this mailbox exists for every setup Outlook email.