(18) Notifications and Emails
- Support Ticket Tutorial -
When Support Tickets are created, it would be good to have the ability to send an email to the person to whom the task has been assigned.
This feature will involve several steps, including downloading a NuGet package and verifying that there is an Assigned To attribute on the Ticket entity so that there will be a user to whom the email will be sent.
1. Establish Email Settings
First you need to establish the configurations that will establish the email communication. Go to Config > App Features. Create a new page.
- Set
Name
to: Email Notifications - Click
Finish
to establish the settings
Add the following Config Items (i.e., click Add Config Item
):
- Set
Name
to: SMTP Server
SetValue Type
to: Text - Set
Name
to: SMTP Port
SetType
to: Number
SetDefault value
to: 587 - Set
Name
to: SMTP Login
SetType
to: Text - Set
Name
to: SMTP Password
SetType
to: Text - Set
Name
to: SMTP Email
SetType
to: Text
Save changes: Ctrl + S
2. Add NuGet Package
At the top of the Config side panel, click Resources
- Click
Add Resource
- Select “Add NuGet Package Assembly Resource”
- Search for MailKit. Click
+ Add Package
- Click
OK
- Save changes: Ctrl + S
3. Establish Custom Code
Go to Code > Custom Code. Create a new page.
- Set
Name of file
to: StmpMailer - Click
Finish
- Copy and paste the following:
Click to expand the code block
using System.Collections.Generic;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
namespace Util
{
internal class SmtpMailer
{
private readonly bool isEnabled;
private readonly string smtpServer;
private readonly int smtpPort;
private readonly string account;
private readonly string password;
private readonly string email;
public static readonly SmtpMailer Default = new SmtpMailer(
App.Features.EmailNotifications.IsEnabled,
App.Features.EmailNotifications.Config.SMTPServer,
App.Features.EmailNotifications.Config.SMTPPort,
App.Features.EmailNotifications.Config.SMTPLogin,
App.Features.EmailNotifications.Config.SMTPPassword,
App.Features.EmailNotifications.Config.SMTPEmail
);
public SmtpMailer(string smtpServer, int smtpPort, string account, string password) : this(true, smtpServer, smtpPort, account, password, account)
{
}
private SmtpMailer(bool isEnabled, string smtpServer, int smtpPort, string account, string password, string email)
{
this.isEnabled = isEnabled;
this.smtpServer = smtpServer;
this.smtpPort = smtpPort;
this.account = account;
this.password = password;
this.email = email;
}
public void SendEmail(string recipient, string subject, string content)
{
SendInternal(new [ ] { recipient }, subject, content, false);
}
public void SendHtmlEmail(string recipient, string subject, string htmlContent)
{
SendInternal(new [ ] { recipient }, subject, htmlContent, true);
}
public void SendMassEmail(IEnumerable<string> recipients, string subject, string content)
{
SendInternal(recipients, subject, content, false);
}
public void SendMassHtmlEmail(IEnumerable<string> recipients, string subject, string htmlContent)
{
SendInternal(recipients, subject, htmlContent, true);
}
public EmailBuilder NewEmail()
{
return new EmailBuilder(this);
}
private void SendInternal(IEnumerable<string> recipients, string subject, string content, bool isHtml, IEnumerable<EmailAttachment> attachments = null, string customSender = null)
{
if (!this.isEnabled)
{
return;
}
attachments = attachments ?? Enumerable.Empty<EmailAttachment>();
try
{
using(var client = new SmtpClient())
{
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(customSender ?? this.email));
foreach(var recipient in recipients)
{
message.To.Add(MailboxAddress.Parse(recipient));
}
message.Subject = subject;
var bodyBuilder = new BodyBuilder();
if (isHtml)
bodyBuilder.HtmlBody = content;
else
bodyBuilder.TextBody = content;
foreach(var a in attachments)
{
bodyBuilder.Attachments.Add(a.FileName, a.ContentProvider(), ContentType.Parse("application/octet-stream"));
}
message.Body = bodyBuilder.ToMessageBody();
client.Connect(smtpServer, smtpPort);
client.Authenticate(new System.Net.NetworkCredential(account, password));
client.Send(message);
client.Disconnect(true);
}
}
catch (Exception e)
{
throw new Exception($"Could not send email [{smtpServer}:{smtpPort}, {account}]", e);
}
}
public class EmailBuilder
{
private string subject = null;
private string content = null;
private string sender = null;
private string replyTo = null;
private bool contentIsHtml = false;
private readonly List<string> recipients = new List<string>();
private readonly List<EmailAttachment> attachments = new List<EmailAttachment>();
private readonly SmtpMailer mailer;
public EmailBuilder(SmtpMailer mailer) { this.mailer = mailer; }
public EmailBuilder AddRecipient(string recipient)
{
this.recipients.Add(recipient);
return this;
}
public EmailBuilder AddRecipients(IEnumerable<string> recipients)
{
this.recipients.AddRange(recipients);
return this;
}
public EmailBuilder SetSender(string senderEmail)
{
this.sender = senderEmail;
return this;
}
public EmailBuilder SetReplyTo(string replyToEmail)
{
this.replyTo = replyToEmail;
return this;
}
public EmailBuilder SetSubject(string subject)
{
this.subject = subject;
return this;
}
public EmailBuilder SetPlainContent(string content)
{
this.content = content;
this.contentIsHtml = false;
return this;
}
public EmailBuilder SetHtmlContent(string content)
{
this.content = content;
this.contentIsHtml = true;
return this;
}
public EmailBuilder AddAttachments(IEnumerable<IDocument> documents)
{
foreach(var d in documents)
{
AddAttachment(d);
}
return this;
}
public EmailBuilder AddAttachments(IEnumerable<IDocumentRevision> documents)
{
foreach(var d in documents)
{
AddAttachment(d);
}
return this;
}
public EmailBuilder AddAttachment(IDocument document)
{
return AddAttachment(document.GetLatestRevision());
}
public EmailBuilder AddAttachment(IDocumentRevision documentRev)
{
if (documentRev == null)
{
return this;
}
this.attachments.Add(new EmailAttachment()
{
FileName = documentRev.FileName,
ContentProvider = () => documentRev.Content
});
return this;
}
public void Send()
{
mailer.SendInternal(recipients, subject, content, contentIsHtml, attachments, sender);
}
}
protected class EmailAttachment
{
public Func<byte[ ]> ContentProvider
{
get;
set;
}
public string FileName
{
get;
set;
}
}
}
}
4. Establish the Text for the Email
Go to Code > Templates. Create a new page.
- Set
Name of Template
to: Text For Email - Verify that
Template Code Language
is set to: Html - Verify that
Model Type
is set to: Entity - Set
Entity
to: Ticket - Click
Finish
Note: Assigned To is used in the template text below. It was created as an attribute on the Ticket entity as part of the Workflow Advanced Topic. If not, it needs to be added to establish the user to whom the email will be sent.
- Copy and paste the following:
Click to expand the code block
@{
var url = App.Urls.EntityPages.Ticket_detail.Generate(Model.Id);
}
<html>
<head>
</head>
<body>
<p>Hi @Model.AssignedTo.Name,</p>
<p>Ticket <a href="@url">@Model.Title</a> has been assigned to you.</p>
<p>Please resolve the issue as quickly as possible.</p>
</body>
</html>
- Click
Finish
5. Establish Business Command to Send Email to Assigned Person
Go to Business > Commands. Create a new page.
- Set
Name
to: Mail Assigned Person - Set
Type
to: Entity Command - Set
Entity
to: Ticket - Click
Finish
- Copy and paste the following:
Click to expand the code block
(ticket, db, ctx) =>
{
//this checks if the ticket is assigned to someone
if (ticket.AssignedTo == null)
{
App.Log.Warning($"The email could not be sent because the ticket {ticket.Title} is not assigned to anyone.");
return;
}
var content = App.Templates.TextForEmail.Render(ticket);
var subject = "Task Assigned";
var mail = ticket.AssignedTo.Email;
//this renders a template with ticket its called on
App.Templates.TextForEmail.Render(ticket);
//this sends email
Util.SmtpMailer.Default.SendHtmlEmail(mail, subject, content);
}
6. Update the Ticket Detail Page
Go to UI > Entity Pages and double-click Ticket Detail
- At the top of the Layout section, click
Add Action
- Verify that
Type
is set to: Execute entity command - Set
Entity Command
to: Mail Assigned Person - In the UI section, set
Button Label
to: Send Email - Set
Icon
to: Envelope - Click
OK
7. Release the App
8. Create a Production Instance
A Production instance should be created in order to send an email to an actual user.
If you have not yet created a Production instance, follow these steps:
- Click
+ Create Instance
- On the Create New Application Instance page, Set Instance Name to: Onboarding Production App
- Verify that Application Version is set to the most recent release
- Verify that Hosting is set to: Jetveo Cloud
- Set Application Type to: Production
- Verify that Authentication is set to: Jetveo account
- Click
Create
If you already have a Production instance, update it to the latest release.
9. Configure Email Settings on the App Overview
- Click
Settings
- Click the
Features
tab - Mark Email Notifications
- Click
Configure
Input the following information (i.e. click Change
) according to your email server:
SMTP Server: TBD
SMTP Port: 587 (This should already be established.)
SMTP Login: TBD
SMTP Password: TBD
SMTP Email: TBD