Esempio n. 1
0
        // Post message example
        public async void PostMessage()
        {
            message = new WebHookMessage
            {
                UserName  = "******",
                AvatarURL = avatarURL,
                Content   = "**Content text.**",
                Embeds    = new List <Embed>
                {
                    new Embed
                    {
                        Color  = 15258703,
                        Author = new Author {
                            Name = "Author", URL = authorURL, IconURL = authorIconURL
                        },
                        Title       = "Title of message.",
                        URL         = messageURL,
                        Description = "Description of message.",
                        Fields      = new List <Field>
                        {
                            new Field {
                                Name = "Name1", Value = "Value1", Inline = true
                            },
                            new Field {
                                Name = "Name2", Value = "Value2", Inline = true
                            },
                            new Field {
                                Name = "Name3", Value = "Value3", Inline = true
                            },
                            new Field {
                                Name = "Name4", Value = "Value4", Inline = true
                            },
                        },
                        Image = new Library.Models.Image {
                            URL = imageURL
                        },
                        Thumbnail = new Thumbnail {
                            URL = imageURL
                        },
                        Footer = new Footer {
                            Text = "Footer text.", IconURL = authorIconURL
                        },
                        Timestamp = WebHookClient.DateTimeToISO8601(2020, 1, 1, 10, 15, 20)
                    }
                },
                TTS = false
            };

            client = new WebHookClient(serverURL);
            // client.WebHookURL = URL;

            int code = await client.PostMessage(message);

            lblStatusCode.Text = code.ToString();
        }
Esempio n. 2
0
        // Post message
        public async Task <int> PostMessage(WebHookMessage message)
        {
            using (HttpClient httpClient = new HttpClient())
            {
                string payload = JsonSerializer.Serialize(message);

                using (HttpResponseMessage httpResponse =
                           await httpClient.PostAsync(WebHookURL, new StringContent(payload, Encoding.UTF8, "application/json")))
                {
                    return((int)httpResponse.StatusCode);
                }
            }
        }
Esempio n. 3
0
        // Post file with some payload example
        public async void PostFile()
        {
            message = new WebHookMessage
            {
                UserName  = "******",
                AvatarURL = avatarURL,
                Content   = "**Content text.**",
                TTS       = false
            };

            client = new WebHookClient(serverURL);

            int code = await client.PostFile(fileName, message);

            lblStatusCode.Text = code.ToString();
        }
Esempio n. 4
0
        // Post file with payload
        public async Task <int> PostFile(string fileName, WebHookMessage message)
        {
            using (HttpClient httpClient = new HttpClient())
            {
                using (MultipartFormDataContent dataContent = new MultipartFormDataContent("CB-" + DateTime.Now.Ticks.ToString("x")))
                {
                    dataContent.Headers.ContentType.MediaType = "multipart/form-data";

                    FileInfo file    = new FileInfo(fileName);
                    string   payload = JsonSerializer.Serialize(message);

                    dataContent.Add(new StreamContent(file.OpenRead()), file.Name, file.Name);
                    dataContent.Add(new StringContent(payload, Encoding.UTF8, "application/json"), "payload_json");

                    using (HttpResponseMessage httpResponse = await httpClient.PostAsync(WebHookURL, dataContent))
                    {
                        return((int)httpResponse.StatusCode);
                    }
                }
            }
        }
Esempio n. 5
0
        public async Task <IHttpActionResult> WebHook([FromBody] WebHookMessage message)
        {
            if (message.Type == "EmotionUpdate")
            {
                const string fromBotAddress = "<Skype Bot ID here>";
                const string toBotAddress   = "<Destination Skype name here>";
                var          text           = resolveEmoji(message.Data);

                using (var client = new ConnectorClient())
                {
                    var outMessage = new Message
                    {
                        To       = new ChannelAccount("skype", address: toBotAddress, isBot: false),
                        From     = new ChannelAccount("skype", address: $"8:{fromBotAddress}", isBot: true),
                        Text     = text,
                        Language = "en",
                    };

                    await client.Messages.SendMessageAsync(outMessage);
                }
            }
            return(Ok());
        }
Esempio n. 6
0
        public void Event_DeSerialize()
        {
            string events_json = @"
[{
""event"":""send"",
""ts"":1355340679,
""url"": ""http://clicked.me"",
""ip"": ""127.0.0.1"",
""user_agent"": ""outlook"",
""msg"":{
 ""ts"":1355340679,
 ""subject"":""Important Stuff"",
 ""email"":""*****@*****.**"",
 ""tags"":[""tag1"",""tag2"",""tag3""],
 ""metadata"":{ ""key1"":""val1"", ""key2"":""val2"" },
 ""opens"":[{""ts"":1355340679},{""ts"":1355412679}],
 ""state"":""sent"",
 ""clicks"":[{""ts"":1355773922,""url"":""http:\/\/www.GitHub.com""}],
 ""_id"":""fc8071b3575e44228d5dd7059349ba10"",
 ""sender"":""*****@*****.**"",
 ""template"":""validTemplate"",
 ""subaccount"":""validSubAccount""}
},{
""event"":""send"",
""ts"":1355340679,
""msg"":{
 ""ts"":1355340679,
 ""subject"":""Important Stuff"",
 ""email"":""*****@*****.**"",
 ""tags"":[],
 ""metadata"":{ },
 ""opens"":[],
 ""clicks"":[],
 ""state"":""sent"",
 ""_id"":""7572c81599d945cfb8dae3a8527f8232"",
 ""sender"":""*****@*****.**"",
 ""template"":""validTemplate"",
 ""subaccount"":""validSubAccount""}
}]";

            var eventTimeDate  = new DateTime(2012, 12, 12, 19, 31, 19);
            int numberOfEvents = 2;

            // Be sure we have two JSON object
            var zot = JSON.Parse <List <object> >(events_json);

            Assert.AreEqual(numberOfEvents, zot.Count);

            // Try parsing out WebHook events
            var events = JSON.Parse <List <WebHookEvent> >(events_json);

            Assert.AreEqual(numberOfEvents, events.Count);
            WebHookEvent e = events[0];

            Assert.AreEqual("http://clicked.me", e.Url);
            Assert.AreEqual("127.0.0.1", e.IP);
            Assert.AreEqual("outlook", e.UserAgent);

            Assert.AreEqual(WebHookEventType.Send, e.Event);
            Assert.AreEqual(eventTimeDate, e.TimeStamp);

            WebHookMessage message = e.Msg;


            Assert.AreEqual("validSubAccount", message.SubAccount);
            Assert.AreEqual("validTemplate", message.Template);
            Assert.AreEqual(WebHookMessageState.Sent, message.State);
            Assert.AreEqual(eventTimeDate, message.TimeStamp);
            Assert.AreEqual("Important Stuff", message.Subject);
            Assert.AreEqual("*****@*****.**", message.Sender);
            Assert.AreEqual("*****@*****.**", message.Email);
            Assert.AreEqual("fc8071b3575e44228d5dd7059349ba10", message.Id);

            Assert.AreEqual(3, message.Tags.Count);
            Assert.AreEqual("tag1", message.Tags[0]);
            Assert.AreEqual("tag2", message.Tags[1]);
            Assert.AreEqual("tag3", message.Tags[2]);

            Assert.AreEqual(2, message.Metadata.Count);
            Assert.AreEqual("key1", message.Metadata[0].Key);
            Assert.AreEqual("val1", message.Metadata[0].Value);
            Assert.AreEqual("key2", message.Metadata[1].Key);
            Assert.AreEqual("val2", message.Metadata[1].Value);

            Assert.AreEqual(2, message.Opens.Count);
            Assert.AreEqual(eventTimeDate, message.Opens[0].TimeStamp);
            Assert.AreEqual(1, message.Clicks.Count);
            Assert.AreEqual("http://www.GitHub.com", message.Clicks[0].Url);
        }
		static async Task SendNotificationAsync(this RequestInfo requestInfo, IPortalObject @object, string @event, Settings.Notifications notificationSettings, ApprovalStatus previousStatus, ApprovalStatus status, CancellationToken cancellationToken)
		{
			// check
			if (@object == null)
				return;

			// prepare settings
			var events = notificationSettings?.Events;
			var methods = notificationSettings?.Methods;
			var emails = notificationSettings?.Emails;
			var emailsByApprovalStatus = notificationSettings?.EmailsByApprovalStatus;
			var emailsWhenPublish = notificationSettings?.EmailsWhenPublish;
			var webHooks = notificationSettings?.WebHooks;
			var category = @object is Content ? (@object as Content).Category : null;
			var emailSettings = category != null ? category.EmailSettings : @object is Organization ? (@object as Organization).EmailSettings : null;

			var parent = @object.Parent;
			while (parent != null)
			{
				Settings.Notifications parentNotificationSettings = null;
				if (parent is Category parentAsCategory)
				{
					parentNotificationSettings = parentAsCategory.Notifications;
					emailSettings = emailSettings ?? parentAsCategory.EmailSettings;
				}
				else if (parent is ContentType parentAsContentType)
				{
					parentNotificationSettings = parentAsContentType.Notifications;
					emailSettings = emailSettings ?? parentAsContentType.EmailSettings;
				}
				else if (parent is Module parentAsModule)
				{
					parentNotificationSettings = parentAsModule.Notifications;
					emailSettings = emailSettings ?? parentAsModule.EmailSettings;
				}
				else if (parent is Organization parentAsOrganization)
				{
					parentNotificationSettings = parentAsOrganization.Notifications;
					emailSettings = emailSettings ?? parentAsOrganization.EmailSettings;
				}

				events = events != null && events.Count > 0 ? events : parentNotificationSettings?.Events;
				methods = methods != null && methods.Count > 0 ? methods : parentNotificationSettings?.Methods;
				emails = emails ?? parentNotificationSettings?.Emails;
				emailsByApprovalStatus = emailsByApprovalStatus ?? parentNotificationSettings?.EmailsByApprovalStatus;
				emailsWhenPublish = emailsWhenPublish ?? parentNotificationSettings?.EmailsWhenPublish;
				webHooks = webHooks ?? parentNotificationSettings?.WebHooks;
				parent = parent.Parent;
			}

			// stop if has no event
			if (events == null || events.Count < 1 || events.FirstOrDefault(e => e.IsEquals(@event)) == null)
				return;

			// prepare parameters
			var sender = (await requestInfo.GetUserProfilesAsync(new[] { requestInfo.Session.User.ID }, cancellationToken).ConfigureAwait(false) as JArray)?.FirstOrDefault();
			var businessObject = @object is IBusinessObject ? @object as IBusinessObject : null;
			var serviceName = ServiceBase.ServiceComponent.ServiceName;
			var objectName = (businessObject as RepositoryBase)?.GetObjectName() ?? @object.GetTypeName(true);
			var contentType = businessObject?.ContentType as ContentType;
			var organization = await (@object.OrganizationID ?? "").GetOrganizationByIDAsync(cancellationToken).ConfigureAwait(false);
			var alwaysUseHtmlSuffix = organization == null || organization.AlwaysUseHtmlSuffix;
			if (organization != null && organization._siteIDs == null)
				await organization.FindSitesAsync(cancellationToken).ConfigureAwait(false);
			var site = organization?.Sites.FirstOrDefault();
			var siteDomain = $"{site?.SubDomain}.{site?.PrimaryDomain}".Replace("*.", "www.").Replace("www.www.", "www.");
			var siteURL = $"http{(site != null && site.AlwaysUseHTTPs ? "s" : "")}://{siteDomain}/";

			// prepare the recipients and notification settings
			var recipientIDs = new List<string>();
			var sendWebHookNotifications = methods?.FirstOrDefault(method => method.IsEquals("WebHook")) != null && webHooks != null && webHooks.EndpointURLs != null && webHooks.EndpointURLs.Count > 0;
			var sendEmailNotifications = methods?.FirstOrDefault(method => method.IsEquals("Email")) != null;
			var emailNotifications = new Settings.EmailNotifications();

			switch (status)
			{
				case ApprovalStatus.Draft:
					recipientIDs = new[] { @object.CreatedID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;

				case ApprovalStatus.Pending:
					recipientIDs = await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Editor, cancellationToken).ConfigureAwait(false);
					if (recipientIDs.Count < 1)
						recipientIDs = await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Moderator, cancellationToken).ConfigureAwait(false);
					if (recipientIDs.Count < 1)
						recipientIDs = await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Administrator, cancellationToken).ConfigureAwait(false);
					if (recipientIDs.Count < 1)
						recipientIDs = new[] { organization.OwnerID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;

				case ApprovalStatus.Rejected:
					recipientIDs = new[] { @object.CreatedID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;

				case ApprovalStatus.Approved:
					recipientIDs = await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Moderator, cancellationToken).ConfigureAwait(false);
					if (recipientIDs.Count < 1)
						recipientIDs = await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Administrator, cancellationToken).ConfigureAwait(false);
					if (recipientIDs.Count < 1)
						recipientIDs = new[] { organization.OwnerID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;

				case ApprovalStatus.Published:
					recipientIDs = (await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Moderator, cancellationToken).ConfigureAwait(false))
						.Concat(await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Administrator, cancellationToken).ConfigureAwait(false))
						.Concat(new[] { @object.CreatedID }).ToList();
					if (recipientIDs.Count < 1)
						recipientIDs = new[] { organization.OwnerID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;

				case ApprovalStatus.Archieved:
					recipientIDs = (await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Moderator, cancellationToken).ConfigureAwait(false))
						.Concat(await @object.WorkingPrivileges.GetUserIDsAsync(PrivilegeRole.Administrator, cancellationToken).ConfigureAwait(false))
						.Concat(new[] { @object.CreatedID, @object.LastModifiedID }).ToList();
					if (recipientIDs.Count < 1)
						recipientIDs = new[] { organization.OwnerID }.ToList();
					emailNotifications = emailsByApprovalStatus != null && emailsByApprovalStatus.ContainsKey($"{status}")
						? emailsByApprovalStatus[$"{status}"]
						: emails;
					break;
			}

			// prepare recipients
			recipientIDs = recipientIDs.Except(new[] { requestInfo.Session.User.ID }).Where(id => !string.IsNullOrWhiteSpace(id)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
			var recipients = await requestInfo.GetUserProfilesAsync(recipientIDs, cancellationToken).ConfigureAwait(false) as JArray;

			//  send app notifications
			try
			{
				var baseMessage = new BaseMessage
				{
					Type = $"Portals#Notification#{@event}",
					Data = new JObject
					{
						{ "Sender", new JObject
							{
								{ "ID", requestInfo.Session.User.ID },
								{ "Name", sender?.Get<string>("Name") ?? "Unknown" }
							}
						},
						{ "Action", @event },
						{ "Info", new JObject
							{
								{ "ServiceName", serviceName },
								{ "ObjectName", objectName },
								{ "SystemID", @object.OrganizationID },
								{ "ObjectID", @object.ID },
								{ "ObjectTitle", @object.Title },
								{ "Status", $"{status}" },
								{ "PreviousStatus", $"{previousStatus}" },
								{ "Time", DateTime.Now },
							}
						}
					}
				};
				await recipients.Select(recipient => recipient.Get<JArray>("Sessions"))
					.Where(sessions => sessions != null)
					.SelectMany(sessions => sessions.Select(session => session.Get<string>("DeviceID")))
					.ForEachAsync(deviceID => Utility.RTUService.SendUpdateMessageAsync(new UpdateMessage(baseMessage) { DeviceID = deviceID }, cancellationToken)).ConfigureAwait(false);
				if (Utility.Logger.IsEnabled(LogLevel.Debug))
					await requestInfo.WriteLogAsync($"Send app notifications successful\r\n{baseMessage.ToJson()}", cancellationToken).ConfigureAwait(false);
			}
			catch (Exception exception)
			{
				await requestInfo.WriteErrorAsync(exception, cancellationToken, "Error occurred while sending app notifications").ConfigureAwait(false);
			}

			// send email notifications
			if (sendEmailNotifications || emailsWhenPublish != null)
			{
				var appURL = $"/portals/initializer?x-request={("{" + $"\"SystemID\":\"{organization?.ID}\",\"ObjectName\":\"{objectName}\",\"ObjectID\":\"{@object.ID}\"" + "}").Url64Encode()}".GetAppURL();
				var normalizedHTMLs = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				var definition = RepositoryMediator.GetEntityDefinition(@object.GetType());
				if (definition != null)
				{
					definition.Attributes.Where(attribute => attribute.IsCLOB != null && attribute.IsCLOB.Value).ForEach(attribute =>
					{
						var value = @object.GetAttributeValue<string>(attribute);
						normalizedHTMLs[attribute.Name] = value?.NormalizeHTML().NormalizeURLs(siteURL);
					});
					if (businessObject?.ExtendedProperties != null && definition.BusinessRepositoryEntities.TryGetValue(businessObject.RepositoryEntityID, out var repositiryEntity))
						repositiryEntity?.ExtendedPropertyDefinitions?.Where(propertyDefinition => propertyDefinition.Mode.Equals(ExtendedPropertyMode.LargeText)).ForEach(propertyDefinition =>
						{
							if (businessObject.ExtendedProperties.TryGetValue(propertyDefinition.Name, out var value))
								normalizedHTMLs[propertyDefinition.Name] = (value as string)?.NormalizeHTML().NormalizeURLs(siteURL);
						});
				}

				var @params = new Dictionary<string, ExpandoObject>(StringComparer.OrdinalIgnoreCase)
				{
					["Organization"] = organization?.ToJson(false, false, json =>
					{
						OrganizationProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
						json["AlwaysUseHtmlSuffix"] = alwaysUseHtmlSuffix;
					}).ToExpandoObject(),
					["Site"] = site?.ToJson(json =>
					{
						SiteProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
						json["Domain"] = siteDomain;
						json["URL"] = siteURL;
					}).ToExpandoObject(),
					["ContentTypeDefinition"] = contentType?.ContentTypeDefinition?.ToJson().ToExpandoObject(),
					["ModuleDefinition"] = contentType?.ContentTypeDefinition?.ModuleDefinition?.ToJson(json =>
					{
						(json as JObject).Remove("ContentTypeDefinitions");
						(json as JObject).Remove("ObjectDefinitions");
					}).ToExpandoObject(),
					["Module"] = contentType?.Module?.ToJson(false, false, json =>
					{
						ModuleProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
					}).ToExpandoObject(),
					["ContentType"] = contentType?.ToJson(false, json =>
					{
						ModuleProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
					}).ToExpandoObject(),
					["ParentContentType"] = contentType?.GetParent()?.ToJson(false, json =>
					{
						ModuleProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
					}).ToExpandoObject(),
					["URLs"] = new JObject
					{
						{ "Public", $"{businessObject?.GetURL() ?? $"~/index"}{(alwaysUseHtmlSuffix ? ".html" : "")}".GetWebURL(siteURL) },
						{ "Portal", $"{businessObject?.GetURL() ?? $"~/index"}{(alwaysUseHtmlSuffix ? ".html" : "")}".GetWebURL($"{Utility.PortalsHttpURI}/~{organization?.Alias}/") },
						{ "Private", appURL },
						{ "Review", appURL }
					}.ToExpandoObject(),
					["HTMLs"] = normalizedHTMLs.ToExpandoObject(),
					["Sender"] = new JObject
					{
						{ "ID", requestInfo.Session.User.ID },
						{ "Name", sender?.Get<string>("Name") ?? "Unknown" },
						{ "Email", sender?.Get<string>("Email") },
						{ "URL", $"/users/profiles/{(sender?.Get<string>("Name") ?? "Unknown").GetANSIUri()}?x-request={("{\"ID\":\"" + requestInfo.Session.User.ID + "\"}").Url64Encode()}".GetAppURL() },
						{ "Location", await requestInfo.GetLocationAsync(cancellationToken).ConfigureAwait(false) },
						{ "IP", requestInfo.Session.IP },
						{ "AppName", requestInfo.Session.AppName },
						{ "AppPlatform", requestInfo.Session.AppPlatform }
					}.ToExpandoObject()
				};

				// add information of the CMS Category
				if (category != null)
					@params["Category"] = category.ToJson(false, false, json =>
					{
						CategoryProcessor.ExtraProperties.ForEach(name => json.Remove(name));
						json.Remove("Privileges");
						json["URL"] = $"{category.GetURL()}{(alwaysUseHtmlSuffix ? ".html" : "")}".GetWebURL(siteURL);
					}).ToExpandoObject();

				// normalize parameters for evaluating
				var language = requestInfo.CultureCode ?? "vi-VN";
				var languages = Utility.Languages.ContainsKey(language) ? Utility.Languages[language] : null;
				var requestExpando = requestInfo.ToExpandoObject(requestInfoAsExpandoObject =>
				{
					requestInfoAsExpandoObject.Set("Body", requestInfo.BodyAsExpandoObject);
					requestInfoAsExpandoObject.Get<ExpandoObject>("Header")?.Remove("x-app-token");
				});
				var objectExpando = @object.ToExpandoObject();
				var paramsExpando = new Dictionary<string, object>(@params.ToDictionary(kvp => kvp.Key, kvp => kvp.Value as object), StringComparer.OrdinalIgnoreCase)
				{
					{ "Event", @event },
					{ "Event-i18n", languages?.Get<string>($"events.{@event}") ?? @event },
					{ "ObjectName", objectName },
					{ "ObjectType", @object.GetTypeName() },
					{ "Status", $"{status}" },
					{ "Status-i18n", languages?.Get<string>($"status.approval.{status}") ?? $"{status}" },
					{ "PreviousStatus", $"{previousStatus}" },
					{ "PreviousStatus-i18n", languages?.Get<string>($"status.approval.{previousStatus}") ?? $"{previousStatus}" },
					{ "Signature", emailSettings?.Signature?.NormalizeHTMLBreaks() },
					{ "EmailSignature", emailSettings?.Signature?.NormalizeHTMLBreaks() }
				}.ToExpandoObject();

				JObject instructions = null;
				if (sendEmailNotifications || ([email protected]("Delete") && businessObject != null && status.Equals(ApprovalStatus.Published) && !status.Equals(previousStatus) && emailsWhenPublish != null))
					try
					{
						instructions = JObject.Parse(await UtilityService.FetchWebResourceAsync($"{Utility.APIsHttpURI}/statics/instructions/portals/{language}.json", cancellationToken).ConfigureAwait(false))?.Get<JObject>("notifications");
					}
					catch (Exception exception)
					{
						await requestInfo.WriteErrorAsync(exception, cancellationToken, "Error occurred while fetching instructions").ConfigureAwait(false);
					}

				// send email message
				if (sendEmailNotifications)
					try
					{
						var subject = emailNotifications?.Subject ?? instructions?.Get<JObject>("emailByApprovalStatus")?.Get<JObject>($"{status}")?.Get<string>("subject") ?? instructions?.Get<JObject>("email")?.Get<string>("subject");
						if (string.IsNullOrWhiteSpace(subject))
							subject = "[{{@params(Organization.Alias)}}] - \"{{@current(Title)}}\" was {{@toLower(@params(Event))}}d";
						var body = emailNotifications?.Body ?? instructions?.Get<JObject>("emailByApprovalStatus")?.Get<JObject>($"{status}")?.Get<string>("body") ?? instructions?.Get<JObject>("email")?.Get<string>("body");
						if (string.IsNullOrWhiteSpace(body))
							body = @"Hi,
							The content that titled as ""<b>{{@current(Title)}}</b>"" ({{@params(ObjectName)}} on <a href=""{{@params(Site.URL)}}"">{{@params(Site.Title)}}</a>) was {{@toLower(@params(Event))}}d by {{@params(Sender.Name)}}.
							You can reach that content by one of these URLs below:
							<ul>
								<li>Public website: <a href=""{{@params(URLs.Public)}}"">{{@params(URLs.Public)}}</a></li>
								<li>CMS portals: <a href=""{{@params(URLs.Portal)}}"">{{@params(URLs.Portal)}}</a></li>
								<li>CMS apps: <a href=""{{@params(URLs.Private)}}"">{{@params(URLs.Private)}}</a></li>
							</ul>
							{{@params(EmailSignature)}}";
						var parameters = $"{subject}\r\n{body}"
							.GetDoubleBracesTokens()
							.Select(token => token.Item2)
							.Distinct(StringComparer.OrdinalIgnoreCase)
							.ToDictionary(token => token, token =>
							{
								return token.StartsWith("@[") && token.EndsWith("]")
									? Extensions.JsEvaluate(token.GetJsExpression(requestExpando, objectExpando, paramsExpando))
									: token.StartsWith("@")
										? token.Evaluate(new Tuple<ExpandoObject, ExpandoObject, ExpandoObject>(requestExpando, objectExpando, paramsExpando))
										: token;
							});
						var message = new EmailMessage
						{
							From = emailSettings?.Sender,
							To = recipients.Select(recipient => recipient.Get<string>("Email")).Where(email => !string.IsNullOrWhiteSpace(email)).Join(";") + (string.IsNullOrWhiteSpace(emailNotifications.ToAddresses) ? "" : $";{emailNotifications.ToAddresses}"),
							Cc = emailNotifications?.CcAddresses,
							Bcc = emailNotifications?.BccAddresses,
							Subject = subject.NormalizeHTMLBreaks().Format(parameters),
							Body = body.NormalizeHTMLBreaks().Format(parameters),
							SmtpServer = emailSettings?.Smtp?.Host,
							SmtpServerPort = emailSettings?.Smtp != null ? emailSettings.Smtp.Port : 0,
							SmtpServerEnableSsl = emailSettings?.Smtp != null && emailSettings.Smtp.EnableSsl,
							SmtpUsername = emailSettings?.Smtp?.User,
							SmtpPassword = emailSettings?.Smtp?.UserPassword,
							CorrelationID = requestInfo.CorrelationID
						};
						await Utility.MessagingService.SendEmailAsync(message, cancellationToken).ConfigureAwait(false);
						var log = "Add an email notification into queue successful" + "\r\n" +
							$"- ID: {message.ID}" + "\r\n" +
							$"- Object: {@object.Title} [{@object.GetType()}#{@object.ID}]" + "\r\n" +
							$"- Event: {@event}" + "\r\n" +
							$"- Status: {status} (previous: {previousStatus})" + "\r\n" +
							$"- Sender: {sender?.Get<string>("Name")} ({sender?.Get<string>("Email")})" + "\r\n" +
							$"- To: {message.To}" + (!string.IsNullOrWhiteSpace(message.Cc) ? $" / {message.Cc}" : "") + (!string.IsNullOrWhiteSpace(message.Bcc) ? $" / {message.Bcc}" : "") + "\r\n" +
							$"- Subject: {message.Subject}";
						await requestInfo.WriteLogAsync(log, cancellationToken).ConfigureAwait(false);
						if (Utility.Logger.IsEnabled(LogLevel.Debug))
							Utility.Logger.LogDebug($"Add an email notification into queue successful\r\n{message.ToJson()}");
					}
					catch (Exception exception)
					{
						await requestInfo.WriteErrorAsync(exception, cancellationToken, "Error occurred while adding an email notification into queue").ConfigureAwait(false);
					}

				// send special email message (when publish)
				if ([email protected]("Delete") && businessObject != null && status.Equals(ApprovalStatus.Published) && !status.Equals(previousStatus) && emailsWhenPublish != null)
					try
					{
						var subject = emailsWhenPublish?.Subject ?? instructions?.Get<JObject>("emailsWhenPublish")?.Get<string>("subject");
						if (string.IsNullOrWhiteSpace(subject))
							subject = "[{{@params(Organization.Alias)}}] - \"{{@current(Title)}}\" was published";
						var body = emailsWhenPublish?.Body ?? instructions?.Get<JObject>("emailsWhenPublish")?.Get<string>("body");
						if (string.IsNullOrWhiteSpace(body))
							body = @"Hi,
							The content that titled as ""<b>{{@current(Title)}}</b>"" ({{@params(ObjectName)}} on <a href=""{{@params(Site.URL)}}"">{{@params(Site.Title)}}</a>) was published by {{@params(Sender.Name)}}.
							You can reach that content by one of these URLs below:
							<ul>
								<li>Public website: <a href=""{{@params(URLs.Public)}}"">{{@params(URLs.Public)}}</a></li>
								<li>CMS portals: <a href=""{{@params(URLs.Portal)}}"">{{@params(URLs.Portal)}}</a></li>
								<li>CMS apps: <a href=""{{@params(URLs.Private)}}"">{{@params(URLs.Private)}}</a></li>
							</ul>
							{{@params(EmailSignature)}}";
						var parameters = $"{subject}\r\n{body}"
							.GetDoubleBracesTokens()
							.Select(token => token.Item2)
							.Distinct(StringComparer.OrdinalIgnoreCase)
							.ToDictionary(token => token, token =>
							{
								return token.StartsWith("@[") && token.EndsWith("]")
									? Extensions.JsEvaluate(token.GetJsExpression(requestExpando, objectExpando, paramsExpando))
									: token.StartsWith("@")
										? token.Evaluate(new Tuple<ExpandoObject, ExpandoObject, ExpandoObject>(requestExpando, objectExpando, paramsExpando))
										: token;
							});
						var message = new EmailMessage
						{
							From = emailSettings?.Sender,
							To = emailsWhenPublish?.ToAddresses,
							Cc = emailsWhenPublish?.CcAddresses,
							Bcc = emailsWhenPublish?.BccAddresses,
							Subject = subject.NormalizeHTMLBreaks().Format(parameters),
							Body = body.NormalizeHTMLBreaks().Format(parameters),
							SmtpServer = emailSettings?.Smtp?.Host,
							SmtpServerPort = emailSettings?.Smtp != null ? emailSettings.Smtp.Port : 0,
							SmtpServerEnableSsl = emailSettings?.Smtp != null && emailSettings.Smtp.EnableSsl,
							SmtpUsername = emailSettings?.Smtp?.User,
							SmtpPassword = emailSettings?.Smtp?.UserPassword,
							CorrelationID = requestInfo.CorrelationID
						};
						await Utility.MessagingService.SendEmailAsync(message, cancellationToken).ConfigureAwait(false);
						var log = "Add an email notification (notify when publish) into queue successful" + "\r\n" +
							$"- ID: {message.ID}" + "\r\n" +
							$"- Object: {@object.Title} [{@object.GetType()}#{@object.ID}]" + "\r\n" +
							$"- Event: {@event}" + "\r\n" +
							$"- Status: {status} (previous: {previousStatus})" + "\r\n" +
							$"- Sender: {sender?.Get<string>("Name")} ({sender?.Get<string>("Email")})" + "\r\n" +
							$"- To: {message.To}" + (!string.IsNullOrWhiteSpace(message.Cc) ? $" / {message.Cc}" : "") + (!string.IsNullOrWhiteSpace(message.Bcc) ? $" / {message.Bcc}" : "") + "\r\n" +
							$"- Subject: {message.Subject}";
						await requestInfo.WriteLogAsync(log, cancellationToken).ConfigureAwait(false);
						if (Utility.Logger.IsEnabled(LogLevel.Debug))
							Utility.Logger.LogDebug($"Add an email notification (notify when publish) into queue successful\r\n{message.ToJson()}");
					}
					catch (Exception exception)
					{
						await requestInfo.WriteErrorAsync(exception, cancellationToken, "Error occurred while adding an email notification (notify when publish) into queue").ConfigureAwait(false);
					}
			}

			// send web-hook notifications
			if (sendWebHookNotifications)
				try
				{
					var signAlgorithm = webHooks.SignAlgorithm ?? "SHA256";
					var signKey = webHooks.SignKey ?? requestInfo.Session.AppID;
					var query = webHooks.AdditionalQuery?.ToExpandoObject().ToDictionary(kvp => kvp.Key, kvp => kvp.Value as string) ?? new Dictionary<string, string>();
					var header = webHooks.AdditionalHeader?.ToExpandoObject().ToDictionary(kvp => kvp.Key, kvp => kvp.Value as string) ?? new Dictionary<string, string>();
					header = new Dictionary<string, string>(header, StringComparer.OrdinalIgnoreCase)
					{
						{ "Event", @event },
						{ "ObjectName", objectName },
						{ "ObjectType", @object.GetTypeName() },
						{ "Status", $"{status}" },
						{ "PreviousStatus", $"{previousStatus}" },
						{ "DeveloperID", requestInfo.Session.DeveloperID },
						{ "AppID", requestInfo.Session.AppID },
						{ "SiteID", site?.ID },
						{ "SiteTitle", site?.Title },
						{ "SiteDomain", siteDomain },
						{ "SiteURL", siteURL },
						{ "OrganizationID", organization?.ID },
						{ "OrganizationTitle", organization?.Title },
						{ "ModuleID", contentType?.Module?.ID },
						{ "ModuleTitle", contentType?.Module?.Title },
						{ "ContentTypeID", contentType?.ID },
						{ "ContentTypeTitle", contentType?.Title }
					};

					var message = new WebHookMessage
					{
						EndpointURL = webHooks.EndpointURLs[0],
						Body = @object.ToJson(json =>
						{
							if (webHooks.GenerateIdentity)
							{
								var id = json.Get<string>("ID");
								if (!string.IsNullOrWhiteSpace(id))
									json["ID"] = id.GenerateUUID();
							}
						}).ToString(Newtonsoft.Json.Formatting.None),
						Query = query,
						Header = header,
						CorrelationID = requestInfo.CorrelationID
					}.Normalize(signAlgorithm, signKey, webHooks.SignatureName, webHooks.SignatureAsHex, webHooks.SignatureInQuery);

					await webHooks.EndpointURLs.ForEachAsync(async (endpointURL, _) =>
					{
						message.ID = UtilityService.NewUUID;
						message.EndpointURL = endpointURL;
						await Utility.MessagingService.SendWebHookAsync(message, cancellationToken).ConfigureAwait(false);
						var log = "Add a web-hook notification into queue successful" + "\r\n" +
							$"- ID: {message.ID}" + "\r\n" +
							$"- Object: {@object.Title} [{@object.GetType()}#{@object.ID}]" + "\r\n" +
							$"- Event: {@event}" + "\r\n" +
							$"- Status: {status} (previous: {previousStatus})" + "\r\n" +
							$"- Endpoint URL: {message.EndpointURL}";
						await requestInfo.WriteLogAsync(log, cancellationToken).ConfigureAwait(false);
						if (Utility.Logger.IsEnabled(LogLevel.Debug))
							Utility.Logger.LogDebug($"Add a web-hook notification into queue successful\r\n{message.ToJson(json => { json["Query"] = message.Query.ToJObject(); json["Header"] = message.Header.ToJObject(); })}");
					}, cancellationToken).ConfigureAwait(false);
				}
				catch (Exception exception)
				{
					await requestInfo.WriteErrorAsync(exception, cancellationToken, "Error occurred while adding a web-hook notification into queue").ConfigureAwait(false);
				}
		}
 public Task SendWebHookAsync(WebHookMessage message, CancellationToken cancellationToken = default)
 => WebHookMessage.SaveAsync(message, WebHookSender.WebHooksPath, cancellationToken);