Technical¶
TramlClient Class¶
The TramlClient class is the main interface for the library. The constructor receives the following parameters:
Parameter Name | Type | Default Value | Description |
---|---|---|---|
app | LimeApplication |
None | An instance of the Lime CRM application |
send_context | SendContext |
None | An instance of the SendContext class. If left empty it will create a send context from the config application_config |
validator | Validator |
Validator | And instance of the Validator class. If left empty it will use the default regexp for email and mobile number validation |
runtime_config | TramlRuntimeConfig |
None | The runtime config. If left empty it will try to load the runtime config from lime_data using the LimeApplication or fallback to a default config |
application_config | TramlApplicationConfig |
None | The application config containing the test settings, API url and key to Lime Marketing. If left empty it will try to load the application config from lime_config using the LimeApplication |
It is possible to use TRAML without a Lime CRM Application if a custom SendContext is supplied together with a config dict with the API info.
Basic Usage¶
Create a TramlClient instance by passing the LimeApplication
. Configs will be resolved automatically
import limepkg_transactional_message_library.traml as traml
tc = traml.TramlClient(application)
# do stuff with traml client
Advanced Usage¶
You can use TramlClient
without a LimeApplication and instead pass runtime and/or application config. This is done by passing TramlApplicationConfig
config to TramlClient
and optionally a custom SendContext
.
- By passing a custom
SendContext
the override and dry run settings will be picked up from the passedSendContext
. - By passing a custom
TramlApplicationConfig
its possible to override all the settings otherwise picked up from the application configuration. A default send context will also be created from those settings if aSendContext
wasn't passed.
Methods Available in TramlClient:¶
send_transactionmail_by_template¶
Send a single transactional email based on a template in Lime Marketing. The Email will be automatically validated to make sure that the recipient and from addresses are valid, that there is a subject and that any attachments are valid.
Parameter Name | Type | Description |
---|---|---|
message | Email |
An Email model |
template_id | int |
The id of the mail template |
Returns | Description |
---|---|
TransactionMailModel |
A model containing the data of the sent Email message if successful |
send_transactionmail¶
Send a single transactional email together with its HTML content. The Email will be automatically validated to make sure that the recipient and from addresses are valid, that there is a subject and that any attachments are valid.
Parameter Name | Type | Description |
---|---|---|
message | Email |
An Email model |
html_content | string |
The HTML content as a string |
text_content | string |
The text content as a string |
Returns | Description |
---|---|
TransactionMailModel |
A model containing the data of the sent Email message if successful |
send_sms¶
Send a single transactional SMS. The SMS will be automatically validated to make sure that the recipient and from numbers are valid and that there is a text content
Parameter Name | Type | Description |
---|---|---|
message | Sms |
An Sms model |
text | str |
The content of the SMS message |
Returns | Description |
---|---|
TransactionSmsModel |
A model containing the data of the sent SMS message if successful |
get_mail_template_id_by_name¶
Get the id of an email template based on its name in Lime Marketing
Parameter Name | Type | Description |
---|---|---|
name | string |
The name of the email template |
Returns | Description |
---|---|
int |
The id of the email template if successful |
create_calendar_link¶
Generate a link to let the recipient add a booking to their calendar. Don't forget to add the link to the email content
Parameter Name | Type | Description |
---|---|---|
calendar_link | CalendarLink |
A CalendarLink model |
Returns | Description |
---|---|
string |
The generated url to download the ical file |
Email Class¶
The Email
class holds all the settings for an email to be sent except the content. The content is passed to TramlClient
as raw html/text or a template when sending an email. The properties for an Email
are:
Parameter Name | Type | Required | Default Value | Description |
---|---|---|---|---|
recipient_name | string |
Yes | The name of the recipient | |
recipient_email | string |
Yes | The email address of the recipient | |
from_name | string |
Yes | The from name of the email | |
from_email | string |
Yes | The from email address of the email | |
subject | string |
Yes | The subject | |
scheduled_send_date | datetime |
No | None | Date and time if the message is scheduled. When omitted or set to None the message will be sent ASAP |
reply_to_email | string |
No | None | Set a reply-to email address to the message. |
sender_name | string |
No | None | The name of the sender. Only for use special use cases. Read more here |
sender_email | string |
No | None | The email address of the sender. Only for use special use cases. Read more here |
attachments | List[Attachment] |
No | [] | A list of attachments |
merge_codes | dict |
No | {} | A dictionary containing merge codes |
external_id | string |
No | Bind the email message to an external id (a Help desk id for example). |
|
track_openings | bool |
No | False | Should message openings be tracked. |
track_link_clicks | bool |
No | False | Should message link clicks be tracked. |
exclude_totaloptouts | bool |
No | False | Don't send if email is present on the total opt-out list |
exclude_publicationoptouts | bool |
No | False | Don't send if email is present on any publication opt-out list |
exclude_previousbounce | bool |
No | True | Don't send if the email has bounced previously |
include_email_data_in_webhook_payload | bool |
No | False | Include the sent email data in the webhook payload |
headers | dict |
No | {} | A dictionary for custom email MIME headers to be set when the email is sent |
options | dict |
No | {} | A dictionary for additional payload parameters to Bedrock |
Sender Name and Sender Email¶
If the sender_name
and sender_email
parameters are used in the Email
model they will be shown as "on behalf of" the from
headers:
The recipient will receive an email like this:
Example of how on behalf of looks in Outlook email client |
Read more about the difference between the From
and Sender
headers here.
Warning
Using sender_name
and sender_email
is an option which you shouldn't have to use very often since it can cause deliverability problems if not used correctly. Make sure its a valid use case before using these settings. An alternative could be to set a more descriptive from_name
Sven Svensson via Super Solution.
Headers¶
With the headers
parameter it's possible to pass custom headers that will be used when the email is sent from Lime Marketing. Typical use cases can be to set priority, autoreply and in reply to and other email meta data that isnt possible to do via the Lime Marketing Bedrock API
Header keys and values must be of type string
or None
The headers can be set directly on the Email
class or by using the helper functions
Warning
Use headers with caution since it may have effect on the deliverability of the email
update_headers¶
Updates the current headers in a safe way - will overwrite existing values.
Parameter Name | Type | Description |
---|---|---|
headers | dict |
New headers to update current headers with |
set_autoreply_headers¶
Sets the headers used to mark an email as an auto reply. This is to tell receiving part to not respond with an auto reply or out of office message.
Parameter Name | Type | Description |
---|---|---|
headers | dict |
New headers to update current headers with |
Headers that are set. There is no "standard" way to this but the following headers are the safest.
Name | Value |
---|---|
Auto-Submitted | auto-replied |
X-Auto-Response-Suppress | All |
Precedence | auto_reply |
set_in_reply_to_headers¶
Sets the headers to mark the email as a part of a conversation/thread. Pass the message id of which this email is a reply to
Parameter Name | Type | Description |
---|---|---|
message_id | str |
The message id of the email this is a reply to. Usually formatted like <[email protected]> |
Headers that are set. There is no "standard" way to this but the following headers are the safest.
Name | Value |
---|---|
In-Reply-To | Value of message_id |
Reference | Value of message_id |
set_priority_headers¶
Sets the different priority headers based on importance where 1 = High, 3 = Normal and 5 = Low. Normal is the same as not setting a priority.
Parameter Name | Type | Description |
---|---|---|
priority | EmailPriority |
High, Normal, Low |
Headers that are set. There is no "standard" way to this but the following headers are the safest.
Name | Value |
---|---|
Importance | High|Normal|Low |
X-Priority | 1|3|5 |
X-MSMail-Priority | High|Normal|Low |
Saving a copy of the sent email message in Lime¶
By setting the include_email_data_in_webhook_payload
parameter to True
when sending an email you will be able to save the email content in Lime CRM.
When the parameter is activated an exact copy of the email that was sent to the recipient will be included in the payload of the transactionmail.sent
webhook. The email content data will be accessible in the TransactionMailContent
property of the webhook payload in the form of a byte array.
You can save this file as a document in Lime CRM by using the TramlEmailMessage
class. Read more about its usage here.
It is important to note that opening the file or clicking any links in the file may result in link-click and opening events to be triggered, just as if the original recipient has opened or clicked the email message.
Sms Class¶
The Sms
class holds all the settings for an sms to be sent except the content. The content is passed to TramlClient
as raw text when sending an sms. The properties for an Sms are:
Parameter Name | Type | Required | Default Value | Description |
---|---|---|---|---|
from_number | string |
Yes | The alphanumeric sender (allowed chars are a-z0-9åäöæøü_- and whitespace and must be 2-11 chars long) | |
destination_number | string |
Yes | The mobile number of the recipient must be country code prefixed | |
scheduled_send_date | datetime |
No | None | Date and time if the message is scheduled. When omitted or set to None the message will be sent ASAP |
max_parts | int |
No | 10 | When the content is longer than 160 chars or special unicode char are used multiple SMS messages needs to be sent to transfer the whole content. With this setting you limit the number of sms messages used but it may result in truncating the content |
delivery_timeout_hours | int |
No | 24 | Timeout in hours for how long the sms should try to be delivered to the recipient |
merge_codes | dict |
No | {} | A dictionary containing merge codes |
options | dict |
No | {} | A dictionary for additional payload parameters to Bedrock |
CalendarLink Class¶
The CalendarLink
class holds the settings to be able to generate a calendar link that can be inserted to your Email content.
The start_date and end_date parameters supports timezone offset. Example given here.
Parameter Name | Type | Required | Default Value | Description |
---|---|---|---|---|
start_date | datetime |
Yes | The start time of the calendar booking | |
end_date | datetime |
Yes | The end time of the calendar booking | |
summary | str |
No | None | The title of the calendar booking |
description | str |
No | None | A short description to insert into the body text of the calendar booking |
location | str |
No | None | The location of the calendar booking |
reminder_before_event | int |
No | 0 | Specify how many minutes before the event the recipient should recieve a reminder |
url | str |
No | None | The url of the event (a Teams meeting link for example) |
TramlEmailMessage class¶
The TramlEmailMessage
class can be used to parse and modify the email data from the TransactionMailContent
property of the transactionmail.sent
webhook payload.
Parameter Name | Type | Required | Default Value | Description |
---|---|---|---|---|
data | str or bytes |
Yes | The email data |
Properties¶
html: Access the email html content as a BeautifulSoup
object. Remember to use the property setter to update the content if you modify it yourself. Raises TramlError
if the message doesn't have any valid html content.
raw_string: Access the email data as a raw unformatted string.
defects: Access possible parsing errors from the email data.
content_type: Get the content type of the message from the message headers.
model_as_formatted_string¶
Returns the email data as a formatted string. If you want to save the message to disk you should use this method.
create_lime_file¶
Returns a lime_file.File with the message data and the message's subject as the filename. The file will have an .eml
extension
remove_opening_tracking_from_email¶
Modifies the email content to remove the opening tracking. Use this before saving the data if you do not want to trigger an open event when viewing the message in Lime.
remove_linkclick_tracking_from_email¶
Modifies the email content to remove the linkclick tracking. Use this before saving the data if you do not want to trigger a linkclick event when viewing the message in Lime.
Example usage:
# transactionmail.sent webhook endpoint
from limepkg_transactional_message_library.traml import TramlEmailMessage
def create_document_from_transactionmail_sent_payload(payload):
byte_data = payload["TransactionMailContent"]
traml_model = TramlEmailMessage(data=byte_data)
# remove the opening tracking before saving the file
# to not trigger a false opening event when viewing the message
traml_model = traml_model.remove_opening_tracking_from_email()
file = traml_model.create_lime_file()
document: LimeObject = self.application.limetypes.document()
document.properties.type.set_by_key("email")
repo: FileStorage = document.properties.document.get_file_repo()
s_file = repo.save(file)
document.properties.document.attach(s_file)
return document
Utils Module¶
The utils module contain helpers methods to help out with reoccurring tasks.
create_attachment_from_document()¶
Creates an TRAML attachment by getting the lime_file from the document Lime Type object
Parameter Name | Type | Description |
---|---|---|
document | lime_type.LimeType |
An lime document object with a document lime_file property |
Returns | Description |
---|---|
Attachment |
A prepared TRAML attachment containing the lime_file data, file name and mime type |
create_attachment_from_lime_file()¶
Creates an TRAML attachment from a lime_file.File
Parameter Name | Type | Description |
---|---|---|
lime_file | lime_file.File |
An lime_file object |
Returns | Description |
---|---|
Attachment |
A prepared TRAML attachment containing the lime_file data, file name and mime type |
Testing¶
When using TRAML it's likely that you have tests that test some logic which will trigger a TRAML email or sms to be sent. Of course, you don't want them to be sent for real and also not cause errors due to lack of configuration. This example shows how you can mock the Bedrock
class sending emails or sms to Lime Marketing and assert that the correct emails and sms should have been sent.
import limepkg_transactional_message_library.traml as traml
import pytest
from unittest.mock import MagicMock
import datetime
import random
@pytest.fixture
def mock_traml_cfg(monkeypatch):
app_cfg = traml.models.TramlApplicationConfig(
api_key="00000000-0000-0000-0000-000000001337",
api_url="https://example.com/bedrock/api/",
)
attr_name = "limepkg_transactional_message_library.config.traml_config.get_traml_application_config"
monkeypatch.setattr(attr_name, MagicMock(return_value=app_cfg))
runtime_cfg = traml.models.TramlRuntimeConfig()
attr_name = "limepkg_transactional_message_library.config.traml_config.get_traml_runtime_config"
monkeypatch.setattr(attr_name, MagicMock(return_value=runtime_cfg))
@pytest.fixture
def mock_bedrock(monkeypatch):
mock_bedrock = MagicMock()
class_name = "limepkg_transactional_message_library.traml.bedrock.Bedrock"
monkeypatch.setattr(class_name, MagicMock(return_value=mock_bedrock))
def email_to_response(*args, **kwargs):
email: traml.models.Email = kwargs["email"]
def _get_json():
return {
"TransactionMailId": random.randint(1, 1000),
"RecipientName": email.recipient_name,
"RecipientEmail": email.recipient_email,
"FromName": email.from_name,
"FromEmail": email.from_email,
"SenderName": email.sender_name,
"SenderEmail": email.sender_email,
"ReplyTo": email.reply_to_email,
"ExternalId": email.external_id,
"Subject": email.subject,
"TrackOpenings": email.track_openings,
"TrackLinkClicks": email.track_link_clicks,
"LinkBaseUrl": "",
"ExcludeTotalOptouts": email.exclude_totaloptouts,
"ExcludePublicationOptouts": email.exclude_publicationoptouts,
"ExcludePreviousBounce": email.exclude_previousbounce,
"IncludeEmailDataInWebhookPayload": email.include_email_data_in_webhook_payload,
"IsInternalMail": False,
"CreationDate": datetime.datetime.now(),
}
mocked_return_value = MagicMock()
mocked_return_value.status_code = 200
mocked_return_value.json = _get_json
mocked_return_value.ok = True
return mocked_return_value
def sms_to_response(*args, **kwargs):
sms: traml.models.Sms = kwargs["sms"]
text: str = kwargs["text"]
def _get_json():
return {
"TransactionSmsId": random.randint(1, 1000),
"DestinationNumber": sms.destination_number,
"FromNumber": sms.from_number,
"MaxParts": sms.max_parts,
"ScheduledSendTime": sms.scheduled_send_date,
"Text": text,
"DeliveryTimeoutHours": sms.delivery_timeout_hours,
"CreationDate": datetime.datetime.now(),
}
mocked_return_value = MagicMock()
mocked_return_value.status_code = 200
mocked_return_value.json = _get_json
mocked_return_value.ok = True
return mocked_return_value
def get_template(*args, **kwargs):
name: str = kwargs["name"]
def _get_json():
return {"Name": name, "Id": random.randint(1, 1000)}
mocked_return_value = MagicMock()
mocked_return_value.status_code = 200
mocked_return_value.json = _get_json
mocked_return_value.ok = True
return mocked_return_value
mock_bedrock.send_transactional_mail.side_effect = email_to_response
mock_bedrock.send_transactional_mail_by_template_id.side_effect = (
email_to_response
)
mock_bedrock.send_transactional_sms.side_effect = sms_to_response
mock_bedrock.get_mailtemplate_by_name.side_effect = get_template
return mock_bedrock
def test_mail_sent(limeapp, mock_bedrock, mock_traml_cfg):
# do stuff that trigger traml email/sms being sent
# mymodule.notify_gillyweed_thieves()
# this is what I expect should have been sent
expected_email = traml.models.Email(
from_name="Sirius Black",
from_email="[email protected]",
subject="Regarding my stolen Gillyweed",
recipient_name="Harry Potter",
recipient_email="[email protected]",
)
expected_sms = traml.models.Sms(
from_number="Hogwarts", destination_number="+46704001337"
)
tc = traml.TramlClient(limeapp)
template = tc.get_mail_template_by_name("Important information")
sent_mail_template = tc.send_transactionmail_by_template(
message=expected_email, template_id=template.id
)
sent_mail_content = tc.send_transactionmail(
message=expected_email,
html_content="html content",
text_content="text content",
)
sent_sms = tc.send_sms(message=expected_sms, text="Return the gillyweed now!")
assert sent_mail_template.recipient_email == expected_email.recipient_email
assert sent_mail_content.recipient_email == expected_email.recipient_email
assert sent_sms.destination_number == expected_sms.destination_number
# assert bedrock sent email with the correct parameters
mock_bedrock.send_transactional_mail_by_template_id.assert_any_call(
email=expected_email, template_id=template.id
)
# assert bedrock sent email with the correct parameters
mock_bedrock.send_transactional_mail.assert_any_call(
email=expected_email,
html_content="html content",
text_content="text content",
)
# assert bedrock sent sms with the correct parameters
mock_bedrock.send_transactional_sms.assert_any_call(
sms=expected_sms, text="Return the gillyweed now!"
)
# use assert_called_with or assert_called_once_with to assert that the LAST
# or ONLY request to bedrock was that one
TransactionMail Webhooks¶
You can see a list of all available Transactionmail webhooks here: https://app.bwz.se/bedrock/example/webhook#events