The email job is a powerful DMSContainer built-in job provided in “the box”. The job email allows to send textual and html email with attachments and related attachments. Moreover, the email job allows to send email with a delay, that’s it, you can plan a send and the job will take care of your message. When you create a new message the email is not send in that moment but is actually sent at the next scheduled run of the job. Usually the email job is scheduled to run each minute or each 5 minutes. A delayed email is send “not before” a specified date, in others word, at the next run after that time stamp. In the next paragraph are listed all the methods available in the email job RPC interface.
Since DMSContainer 4.0.x the Email Module act as DataSource for the EventStreams Module. These means that while its normal activities run, the Email Module emits events in particular queues to notify these activities. In particular any sent email message create a new event in a queue named “queues.jobs..email.sent” and for each sending error creates a new event in a queue named “queues.jobsemail.error”. By default the email module is configured with the name “emails”, so, by default, you will get these messages in the queues “queues.jobs.email.sent” and “queues.jobs.email.sent”. This particular integration open endless possibilities, for instance you can choose to start some process only if an email cannot be sent, o just plug further elaborations when an email is sent and so on. Really, sky is the limit.
The email job uses JWT to do users authentication and authorization. The login
method inherited from the Auth Module can be called even in the Email Module and returns a token valid for the next 30 minutes. Any other method but login
, requires a valid token
as the first parameter.
The Email Module sends email on behalf of a DMS user. It sends email using a specific SMTP configuration called “User’s Sender”. To be able to send emails a DMS user must be configured with a User’s Sender. Check the SetUserSender API for more info.
If you send an email using a professional SMTP client there are two optional features that can be configured:
If you check “delivery receipt”, the “Return-Receipt-To” header field is added to your email. For example:
Return-Receipt-To: "bit Time Professionals" <professionals@bittime.it>
If you check “read receipt”, the “Disposition-Notification-To” header field is added to your email. For example:
Disposition-Notification-To: "bit Time Professionals" <professionals@bittime.it>
The delivery receipt (header Return-Receipt-To) is a request for the receiving mail server to send a Delivery Status Notification (a.k.a. DSN) as soon as it receives the email.
The read receipt (header Disposition-Notification-To) is a request for the receiving email client to send a DSN as soon as the person opens the email.
Both header fields can be added checking “Ask for return receipt” in the “Sender Configuration” form of each user.
However, while the Email Modules does its best to be standard compliant, as the RFC says, ““he presence of a Disposition-Notification-To header in a message is merely a request for an MDN. The recipients’ user agents are always free to silently ignore such a request. Alternatively, an explicit denial of the request for information about the disposition of the message may be sent using the “denied” disposition in an MDN.”
When you send an email to a subscriber and they click ‘Reply’, the reply message is typically sent to the email address listed in the From
header.
A Reply-To
address is identified by inserting the Reply-To
header in your email. It is the email address that the reply message is sent when you want the reply to go to an email address that is different than the From
address.
In the example below, ‘professionals@bittime.it’ is the Reply-To
address. When a subscriber clicks ‘Reply’, the reply message is sent to ‘professionals@bittime.it’ instead of ‘marketing@bittime.it’.
From: marketing@bittime.com
To: pparker@spideycorporation.com
Reply-To: professionals@sampledomain.com
DMSContainer’ Email Module allows to set a custom Reply-To
header setting the property msgreplytolist
as defined in the API. Check the samples to know all the details.
The Email Module provides a number of APIs to manage messages, bulk messages (a.k.a. emailing list or newslettera) users and senders.
Allows to log in into the system. It’s inherited by the Auth Module.
Defines a sender for an user. If a user has a sender that user can send emails, otherwise cannot send emails.
PARAMS
token: A valid token
userid: Userid of the user to whom define the sender
obj: A json object with the following properties:
- senderaddress: The email that will be used to send the email (e.g. peter.parker@bittime.it);
- smtphost: The smtphost that the user will use to send emails (e.g. smtp.gmail.com);
- smtpport: The smtpport where the smtpserver listen (e.g. 587);
- smtpusername: The SMTP username (e.g. peter.parker);
- smtppassword: The SMTP password (e.g. spidey2000);
- smtpusessl: A boolean value which defines if the SMTP server needs SSL or not
- smtpsslversion: One of the following values (as string) depending from the SMTP provider:
SSLv2, SSLv23, SSLv3, TLSv1, TLSv1_1, TLSv1_2
This field is ignored if smtpusessl = false
RETURNS
none
REQUIRED ROLES
admin
var
lResp: IJSONRPCResponse;
lJSONParam: TJsonObject;
lJSON: TJsonObject;
lUserID: Integer;
lNotification: IJSONRPCNotification;
begin
lNotification := TJSONRPCNotification.Create('SetUserSender');
lUserID := 1;
lNotification.Params.Add(fToken);
lNotification.Params.Add(lUserID);
lJSON := TJsonObject.Create;
lNotification.Params.Add(lJSON);
lJSON.S['senderaddress'] := 'd.teti@bittime.it';
lJSON.S['smtphost'] := 'smtp.gmail.com';
lJSON.I['smtpport'] := 587;
lJSON.S['smtpusername'] := 'd.teti@bittime.it';
lJSON.S['smtppassword'] := 'your password';
lJSON.B['smtpusessl'] := True;
lJSON.S['smtpsslversion'] := 'TLSv1';
lResp := fExecutor.ExecuteNotification(lNotification);
ShowMessage('Sender set for user');
end;
Remove the sender for an user so that it cannot send emails anymore
PARAMS
token: A valid token
userid: The userid of the user to whom remove the sender
RETURNS
none
REQUIRED ROLES
admin
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lJSON: TJsonObject;
lUserID: Integer;
begin
lUserID := 99;
lReq := TJSONRPCRequest.Create(1234, 'removeusersender');
lReq.Params.Add(fToken);
lReq.Params.Add(lUserID);
lResp := fExecutor.ExecuteRequest(lReq);
ShowMessage('User sender removed');
end;
Add a simple message (without attachments) into the messages queue
PARAMS
token: A valid token
obj: A json object with the following properties:
- msgtolist: comma separated email address
- msgcclist: comma separated email address
- msgbcclist: comma separated email address
- msgreplytolist: comma separated email address
- msgsubject: the subject of the email
- istest: if true the email is not actually sent, used for debug (optional, default false)
- msgbody: the textual body of the email (optional, default empty)
- msgbodyhtml: the html body of the email (optional, default empty)
- msgnote: note for the others user, doesn't affect the sent email (optional, default empty)
RETURNS
messageid: how the message is identified by the system
REQUIRED ROLES
sender
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lJSONParam: TJsonObject;
begin
lReq := TJSONRPCRequest.Create(1234, 'sendmessage');
lReq.Params.Add(Token);
lJSONParam := TJsonObject.Create;
try
lJSONParam.S['msgbody'] := 'This is the message sent at ' + DateTimeToStr(Now);
lJSONParam.S['msgbodyhtml'] := 'This is the HTML message sent at ' + DateTimeToStr(Now);
lJSONParam.S['msgsubject'] := '[DMS EMAIL CLIENT TEST] This is the subject';
lJSONParam.S['msgtolist'] := 'larry@google.com,sergey@google.com,timothy@apple.com';
lJSONParam.S['msgcclist'] := ''; //optional
lJSONParam.S['msgbcclist'] := 'larry@oracle.com'; //optional
lJSONParam.S['msgreplytolist'] := '"Spidey" <pparker@email.com>'; //optional
lReq.Params.Add(lJSONParam);
except
lJSONParam.Free;
raise;
end;
lResp := fRPCExecutor.ExecuteRequest(lReq);
ShowMessage('Message queued with messageid = ' + lResp.ResultAsJSONObject.I['messageid'].ToString);
Retrieve a message by id
PARAMS
token: A valid token
messageid: The id of the message to retrieve
RETURNS
obj: The retrieved message
Raises an exception: if the message doesn't exist
REQUIRED ROLES
If `sender` the user can retrieve only its messages
If `admin` or `monitor` the user can retrieve all the messages
import dmsproxy
proxy = dmsproxy.DMSProxy("https://localhost:443/emailrpc")
proxy.login("user_sender", "pwd1")
message = proxy.get_message_by_id(472)
print(message)
The output is shown below
{
"msgbody": "This is a text body",
"msgbodyhtml": "This is a <b>html</b> body",
"msgnote": "",
"msgfromaddress": "peter.parker79@libero.it",
"smtphost": "smtp.libero.it",
"smtpport": 465,
"smtpusername": "peter.parker79@libero.it",
"smtppassword": "<hidden>",
"smtpusessl": True,
"smtpsslversion": "TLSv1_1",
"id": 472,
"senderuserid": 1,
"owneruserid": 1,
"msgtolist": "d.teti@bittime.it,daniele.teti@gmail.com",
"msgcclist": "",
"msgbcclist": "",
"msgsubject": "Here's the files you need",
"status": "SENT",
"createdat": "2019-01-09T12:07:13.853Z",
"sentat": "2019-01-09T12:07:19.281Z",
"sendnotbeforeof": None,
"retrycount": 0,
"lasterror": "",
"istest": False
}
Deletes a message by id
PARAMS
token: A valid token
messageid: The id of the message to delete
RETURNS
none
Raises an exception: if the message doesn't exist
REQUIRED ROLES
If `sender` the user can retrieve delete its non `SENT` messages
If `admin` the user can delete any message in any state
import dmsproxy
proxy = dmsproxy.DMSProxy("https://localhost:443/emailrpc")
proxy.login("user_sender", "pwd1")
proxy.delete_message(472)
Retrieve a message for a user
PARAMS
token: A valid token
username: The username of the messages
RETURNS
items: A list of messages
Raises an exception: if the message doesn't exist
REQUIRED ROLES
admin or monitor
import dmsproxy
proxy: dmsproxy.DMSProxy = dmsproxy.DMSProxy("https://localhost:443/emailrpc")
proxy.login("user_monitor", "pwd1")
messages = proxy.get_messages_by_username("user_sender")
print(messages)
The output is shown below
[
{
"msgbody": "hello body",
"msgbodyhtml": "This is a <b>html</b> body",
"msgnote": "",
"msgfromaddress": "d.teti@bittime.it",
"smtphost": "smtp.gmail.com",
"smtpport": 587,
"smtpusername": "d.teti@bittime.it",
"smtppassword": "<hidden>",
"smtpusessl": True,
"smtpsslversion": "TLSv1",
"id": 380,
"senderuserid": 1,
"owneruserid": 1,
"msgtolist": "d.teti@bittime.it",
"msgcclist": "",
"msgbcclist": "",
"msgsubject": "This is the subject #1",
"status": "SENT",
"createdat": "2018-01-04T16:40:33.832Z",
"sentat": "2019-01-04T16:41:02.060Z",
"sendnotbeforeof": "2018-01-04T16:40:32.740Z",
"retrycount": 0,
"lasterror": "",
"istest": False
},
"...other json objects..."
]
Retrieve messages by status
PARAMS
token: A valid token
statuslist: A json array containing statuses (e.g. ['TO_SEND','SENT'])
RETURNS
items: A list of messages
Raises an exception: if the statuses are not valid
REQUIRED ROLES
admin or monitor
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lJSONParam: TJsonArray;
begin
lReq := TJSONRPCRequest.Create(1234, 'getmessagesbystatus');
lReq.Params.Add(Token);
lJSONParam := TJsonArray.Create;
try
lJSONParam.Add('ERROR');
lJSONParam.Add('TO_SEND');
// lJSONParam.Add('SENT');
lJSONParam.Add('NOT_COMPLETED');
lReq.Params.Add(lJSONParam);
except
lJSONParam.Free;
raise;
end;
lResp := fRPCExecutor.ExecuteRequest(lReq);
//mtMessages is a TFDMemTable
mtMessages.Close;
mtMessages.Open;
mtMessages.LoadFromJSONArray(lResp.ResultAsJSONArray);
import dmsproxy
proxy: dmsproxy.DMSProxy = dmsproxy.DMSProxy("https://localhost:443/emailrpc")
proxy.login("user_monitor", "pwd1")
messages = proxy.get_messages_by_status(['TO_SEND','SENT'])
print(messages)
The output is shown below
[
{
msgbody: "This is a text body",
msgbodyhtml: "This is a <b>html</b> body",
msgnote: "",
msgfromaddress: "d.teti@bittime.it",
smtphost: "smtp.gmail.com",
smtpport: 587,
smtpusername: "d.teti@bittime.it",
smtppassword: "",
smtpusessl: True,
smtpsslversion: "TLSv1",
id: 380,
senderuserid: 1,
owneruserid: 1,
msgtolist: "d.teti@bittime.it",
msgcclist: "",
msgbcclist: "",
msgsubject: "This is the subject #1",
status: "SENT",
createdat: "2019-01-04T16:40:33.832Z",
sentat: "2019-01-04T16:41:02.060Z",
sendnotbeforeof: "2019-01-04T16:40:32.740Z",
retrycount: 0,
lasterror: "",
istest: False,
},
"...other json objects...",
];
Create message with the abilities to contains attachments, into the messages queue.
The message must be completed using completemessage
before to be enqueued in the message queue. If attachments are needed, issue a call to addattachmenttomessage
for each attachment.
PARAMS
token: A valid token
obj: A json object with the following properties:
- msg_to_list: comma separated email address
- msg_cc_list: comma separated email address
- msg_bcc_list: comma separated email address
- msg_subject: the subject of the email
- is_test: if true the email is not actually sent, used for debug (optional, default false)
- msg_body: the textual body of the email (optional, default empty)
- msg_body_html: the html body of the email (optional, default empty)
- msg_note: note for the others user, doesn't affect the sent email (optional, default empty)
- send_not_before_of: do not send the message before this timestamp (iso format)
RETURNS
messageid: how the message is identified by the system
REQUIRED ROLES
sender
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lJSONParam: TJsonObject;
begin
lReq := TJSONRPCRequest.Create(1234, 'createmessage');
lReq.Params.Add(Token);
lJSONParam := TJsonObject.Create;
try
lJSONParam.S['msgbody'] := 'This is the message sent at ' + DateTimeToStr(Now);
lJSONParam.S['msgbodyhtml'] := 'This is the HTML message sent at ' + DateTimeToStr(Now);
lJSONParam.S['msgsubject'] := '[DMS EMAIL CLIENT TEST] This is the subject';
lJSONParam.S['msgtolist'] := 'larry@google.com,sergey@google.com,timothy@apple.com';
lJSONParam.S['msgcclist'] := '';
lJSONParam.S['msgbcclist'] := 'larry@oracle.com';
lReq.Params.Add(lJSONParam);
except
lJSONParam.Free;
raise;
end;
lResp := fRPCExecutor.ExecuteRequest(lReq);
ShowMessage('Message queued with messageid = ' + lResp.ResultAsJSONObject.I['messageid'].ToString);
Inform the system that a message created with createmessage
is ready to be sent.
If attachments are needed, issue a call to addattachmenttomessage
for each attachment before the call to completemessage
.
PARAMS
token: A valid token
messageid: The messageid of the message that must be completed
RETURNS
none
REQUIRED ROLES
sender
See example about addattachmenttomessage
addattachmenttomessage
must be called after createmessage
and before completemessage
for each attachment needed.
PARAMS
token: A valid token
messageid: The messageid to whom the attachment must be added
isrelated: A boolean value. If True means that the attachment is used into the HTML body of the message obj: A json object with the following properties:
- filename: the name of the file
- filedata: the contents of the datafile encoded as base64
RETURNS
none
REQUIRED ROLES
sender
var
lReq: IJSONRPCRequest;
lResp: IJSONRPCResponse;
lJSONParam: TJsonObject;
lMsgID: Integer;
lAttachment: String;
lAttachments: TArray<String>;
begin
// create the message
lReq := TJSONRPCRequest.Create(1234, 'createmessage');
lReq.Params.Add(Token);
lJSONParam := TJsonObject.Create;
try
lJSONParam.S['msgbody'] := 'This message has some attachments';
lJSONParam.S['msgbodyhtml'] := 'This message <span style="color: red">has</span> some attachments';
lJSONParam.S['msgsubject'] := '[DMS EMAIL CLIENT TEST] This is the subject';
lJSONParam.S['msgtolist'] := 'bill@microsoft.com';
lJSONParam.S['msgcclist'] := 'larry@oracle.com,johndoe@gmail.com';
except
lJSONParam.Free;
raise;
end;
lReq.Params.Add(lJSONParam);
lResp := fRPCExecutor.ExecuteRequest(lReq);
lMsgID := lResp.ResultAsJSONObject.I['messageid'];
// add all attachments
lAttachments := ['file1.png', 'file2.pdf'];
for lAttachment in lAttachments do
begin
lReq := TJSONRPCRequest.Create(1234, 'addattachmenttomessage');
lReq.Params.Add(Token);
lReq.Params.Add(lMsgID);
lReq.Params.Add(false); // is not related
lJSONParam := TJsonObject.Create;
try
lJSONParam.S['filename'] := lAttachment;
lJSONParam.S['filedata'] := TNetEncoding.Base64.EncodeBytesToString(TFile.ReadAllBytes(lAttachment));
lReq.Params.Add(lJSONParam);
except
lJSONParam.Free;
raise;
end;
fRPCExecutor.ExecuteRequest(lReq);
end;
// complete the message
lReq := TJSONRPCRequest.Create(1234, 'completemessage');
lReq.Params.Add(Token);
lReq.Params.Add(lResp.ResultAsJSONObject.I['messageid']);
fRPCExecutor.ExecuteRequest(lReq);
end;
This example uses the built-in python proxy for the DMSContainer email job available in the Python module dmsproxy.py
.
import dmsproxy
from pathlib import Path
proxy = dmsproxy.DMSProxy("https://localhost:443/emailrpc")
proxy.login("user_sender", "pwd1")
msg = {
"msgtolist": "larry@oracle.com",
"msgbody": "This is a text body",
"msgbodyhtml": "This is a <b>html</b> body",
"msgsubject": "Here's the files you need",
"is_test": False
}
#create the message
message_id = proxy.create_message(**msg)
# add first attachment
att1 = Path(__file__).parent.joinpath('testdata').joinpath('file1.png')
proxy.add_attachment(message_id, str(att1), False)
# add second attachment
att2 = Path(__file__).parent.joinpath('testdata').joinpath('file2.pdf')
proxy.add_attachment(message_id, str(att2), False)
# complete the message
proxy.complete_message(message_id)
print("Message correctly enqueued with messageid: " + str(message_id))
Inform the system that a message created with createmessage
is ready to be sent.
If attachments are needed, issue a call to addattachmenttomessage
for each attachment before the call to completemessage
.
PARAMS
token: A valid token
messageid: The messageid of the message that must be completed
RETURNS
none
REQUIRED ROLES
sender
See example about addattachmenttomessage
Send a bulk message to a list of email addresses. The message can have also a list of attachments. The body and the attachments can be template driven. If the message contains a template (body or attachment) reportdata must contains the data to generate them.
PARAMS
token: A valid token
aMetaMessage: A json object with the following properties:
- msgsubject: the subject of the email (can contains template tags)
- istest: if true the email is not actually sent, used for debug (optional, default false)
- msgbody: the textual body of the email (can contains template tags) (optional, default empty)
- msgbodyhtml: the html body of the email (can contains template tags) (optional, default empty)
- msgnote: note for the others user, doesn't affect the sent email (optional, default empty)
- attachments: json array of "attachment" json object. Each attachment has the following properties
- filename: the filename shown to the user (can contains template tags)
- filedata: the contents of the file. If `is_template` is `true` the filedata **must** be a docx file
- is_template: boolean value. If true the `filedata` must be a docx file and the attached file will be a PDF.
aBulkMessagesData: A json object with the following properties:
- meta: ajsonobject containing data shared for all the templates. No specific format is enforced.
- recipients: a json array which contains the recipents data used for each message. Will be generated one message for each `recipient`. Each item contains the following properties:
- msgtolist: comma separated email address
- msgcclist: comma separated email address
- msgbcclist: comma separated email address
- refid: the reference id used to identify the return; usually is the primary key of the database table which should track the sent messages.
- items: a json array which contains the data used for each message. items can be empty or zero length, but if it exists then must be of the same size of the `recipents` array. Each item can contains any property and those data will be used for each "template" part of the message (subject, body, body_html, attachment etc so on).
RETURNS
messagesid: an array of integer containing the generated messages id.
REQUIRED ROLES
sender
procedure TMainForm.btnSendBulkMessagesClick(Sender: TObject);
var
lMetaMessage: TJSONObject;
lMessageData: TJSONObject;
lRecipient: TJSONObject;
lItem: TJSONObject;
lJObj: TJSONObject;
begin
/// Let's prepare the metadata of the email message
/// ////////////////////////////////////////////////////////
lMetaMessage := TJSONObject.Create;
try
lMetaMessage.S['msgsubject'] :=
'[DMSContainer Email TEST] {{meta.title}} - This email is for {{data.nome}} {{data.cognome}}';
lMetaMessage.S['msgbody'] := '';
lMetaMessage.S['msgbodyhtml'] := TFile.ReadAllText('template_invito_email_10.html', TEncoding.UTF8);
lMetaMessage.S['msgnote'] := '';
lMetaMessage.B['istest'] := false;
FillWithAttachments(lMetaMessage);
except
lMetaMessage.Free;
raise;
end;
/// Let's prepare the actual recipients data
/// ////////////////////////////////////////////////////////
lMessageData := TJSONObject.Create;
try
/// META
lMessageData.O['meta'].S['title'] := 'This is the title';
/// RECIPIENTS
lRecipient := lMessageData.A['recipients'].AddObject;
lRecipient.S['msgtolist'] := 'theboss@mydomain.it';
lRecipient.S['msgcclist'] := 'thebossassistantoffice@mydomain.it';
lRecipient.S['msgbcclist'] := 'mysecretemail@mydomain.it';
lRecipient.I['refid'] := 1;
lRecipient := lMessageData.A['recipients'].AddObject;
lRecipient.S['msgtolist'] := 'thecustomer@mydomain.it';
lRecipient.I['refid'] := 2;
lRecipient := lMessageData.A['recipients'].AddObject;
lRecipient.S['msgtolist'] := 'thesupplier@mydomain.it';
lRecipient.I['refid'] := 3;
/// ITEMS
lItem := lMessageData.A['items'].AddObject;
lItem.S['nome'] := 'Daniele';
lItem.S['cognome'] := 'Teti';
lItem.S['cerimonia'] := '35° Anniversario Ufficio Affari Vari ed Eventuali';
lItem.S['luogo_cerimonia'] := 'P.zza 1° Maggio';
lItem.S['data_cerimonia'] := '1° maggio 2019';
lItem := lMessageData.A['items'].AddObject;
lItem.S['nome'] := 'Peter';
lItem.S['cognome'] := 'Parker';
lItem.S['cerimonia'] := '35° Anniversario Ufficio Affari Vari ed Eventuali';
lItem.S['luogo_cerimonia'] := 'P.zza 1° Maggio';
lItem.S['data_cerimonia'] := '1° maggio 2019';
lItem := lMessageData.A['items'].AddObject;
lItem.S['nome'] := 'Sue';
lItem.S['cognome'] := 'Storm';
lItem.S['cerimonia'] := '35° Anniversario Ufficio Affari Vari ed Eventuali';
lItem.S['luogo_cerimonia'] := 'P.zza 1° Maggio';
lItem.S['data_cerimonia'] := '1° maggio 2019';
except
lMessageData.Free;
raise;
end;
lJObj := fEmailRPCProxy.BulkSendMessages(Token, lMetaMessage, lMessageData);
try
ShowMessage('Messages queued: ' + lJObj.A['messagesid'].ToJSON(false));
finally
lJObj.Free;
end;
end;
procedure TMainForm.FillWithAttachments(aMetaMessage: TJSONObject);
var
lJSON: TJSONObject;
begin
lJSON := aMetaMessage.A['attachments'].AddObject();
// Parametric attachment template as docx
lJSON.S['filename'] := 'Invito Mr {{data.cognome}} {{data.nome}}.pdf';
lJSON.S['filedata'] := FileToBase64String('template_invito_email_10.docx');
lJSON.B['istemplate'] := true;
// Non parametric attachment
lJSON := aMetaMessage.A['attachments'].AddObject();
lJSON.S['filename'] := 'Dressing Code for Mr {{data.cognome}} {{data.nome}}.pdf';
lJSON.S['filedata'] := FileToBase64String('non_template_attachment.pdf');
lJSON.B['istemplate'] := false;
end;