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¶
Complete template in HTML
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¶
Complete template in HTML
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.