Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

WorkflowMessageService.cs

using System.Net;
using Nop.Core;
using Nop.Core.Domain.Blogs;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Forums;
using Nop.Core.Domain.Messages;
using Nop.Core.Domain.News;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Shipping;
using Nop.Core.Domain.Vendors;
using Nop.Core.Events;
using Nop.Services.Affiliates;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Stores;

namespace Nop.Services.Messages;

/// 
/// Workflow message service
/// 
public partial class WorkflowMessageService : IWorkflowMessageService
{
    #region Fields

    protected readonly CommonSettings _commonSettings;
    protected readonly EmailAccountSettings _emailAccountSettings;
    protected readonly IAddressService _addressService;
    protected readonly IAffiliateService _affiliateService;
    protected readonly ICustomerService _customerService;
    protected readonly IEmailAccountService _emailAccountService;
    protected readonly IEventPublisher _eventPublisher;
    protected readonly ILanguageService _languageService;
    protected readonly ILocalizationService _localizationService;
    protected readonly IMessageTemplateService _messageTemplateService;
    protected readonly IMessageTokenProvider _messageTokenProvider;
    protected readonly IOrderService _orderService;
    protected readonly IProductService _productService;
    protected readonly IQueuedEmailService _queuedEmailService;
    protected readonly IStoreContext _storeContext;
    protected readonly IStoreService _storeService;
    protected readonly ITokenizer _tokenizer;
    protected readonly MessagesSettings _messagesSettings;

    #endregion

    #region Ctor

    public WorkflowMessageService(CommonSettings commonSettings,
        EmailAccountSettings emailAccountSettings,
        IAddressService addressService,
        IAffiliateService affiliateService,
        ICustomerService customerService,
        IEmailAccountService emailAccountService,
        IEventPublisher eventPublisher,
        ILanguageService languageService,
        ILocalizationService localizationService,
        IMessageTemplateService messageTemplateService,
        IMessageTokenProvider messageTokenProvider,
        IOrderService orderService,
        IProductService productService,
        IQueuedEmailService queuedEmailService,
        IStoreContext storeContext,
        IStoreService storeService,
        ITokenizer tokenizer,
        MessagesSettings messagesSettings)
    {
        _commonSettings = commonSettings;
        _emailAccountSettings = emailAccountSettings;
        _addressService = addressService;
        _affiliateService = affiliateService;
        _customerService = customerService;
        _emailAccountService = emailAccountService;
        _eventPublisher = eventPublisher;
        _languageService = languageService;
        _localizationService = localizationService;
        _messageTemplateService = messageTemplateService;
        _messageTokenProvider = messageTokenProvider;
        _orderService = orderService;
        _productService = productService;
        _queuedEmailService = queuedEmailService;
        _storeContext = storeContext;
        _storeService = storeService;
        _tokenizer = tokenizer;
        _messagesSettings = messagesSettings;
    }

    #endregion

    #region Utilities

    /// 
    /// Get active message templates by the name
    /// 
    /// Message template name
    /// Store identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the list of message templates
    /// 
    protected virtual async Task> GetActiveMessageTemplatesAsync(string messageTemplateName, int storeId)
    {
        //get message templates by the name
        var messageTemplates = await _messageTemplateService.GetMessageTemplatesByNameAsync(messageTemplateName, storeId);

        //no template found
        if (!messageTemplates?.Any() ?? true)
            return new List();

        //filter active templates
        messageTemplates = messageTemplates.Where(messageTemplate => messageTemplate.IsActive).ToList();

        return messageTemplates;
    }

    /// 
    /// Get EmailAccount to use with a message templates
    /// 
    /// Message template
    /// Language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the emailAccount
    /// 
    protected virtual async Task GetEmailAccountOfMessageTemplateAsync(MessageTemplate messageTemplate, int languageId)
    {
        var emailAccountId = await _localizationService.GetLocalizedAsync(messageTemplate, mt => mt.EmailAccountId, languageId);
        //some 0 validation (for localizable "Email account" dropdownlist which saves 0 if "Standard" value is chosen)
        if (emailAccountId == 0)
            emailAccountId = messageTemplate.EmailAccountId;

        var emailAccount = (await _emailAccountService.GetEmailAccountByIdAsync(emailAccountId) ?? await _emailAccountService.GetEmailAccountByIdAsync(_emailAccountSettings.DefaultEmailAccountId)) ??
                           (await _emailAccountService.GetAllEmailAccountsAsync()).FirstOrDefault();
        return emailAccount;
    }

    /// 
    /// Ensure language is active
    /// 
    /// Language identifier
    /// Store identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the return a value language identifier
    /// 
    protected virtual async Task EnsureLanguageIsActiveAsync(int languageId, int storeId)
    {
        //load language by specified ID
        var language = await _languageService.GetLanguageByIdAsync(languageId);

        if (language == null || !language.Published)
        {
            //load any language from the specified store
            language = (await _languageService.GetAllLanguagesAsync(storeId: storeId)).FirstOrDefault();
        }

        if (language == null || !language.Published)
        {
            //load any language
            language = (await _languageService.GetAllLanguagesAsync()).FirstOrDefault();
        }

        if (language == null)
            throw new Exception("No active language could be loaded");

        return language.Id;
    }

    /// 
    /// Get email and name to send email for store owner
    /// 
    /// Message template email account
    /// Email address and name to send email fore store owner
    protected virtual async Task<(string email, string name)> GetStoreOwnerNameAndEmailAsync(EmailAccount messageTemplateEmailAccount)
    {
        var storeOwnerEmailAccount = _messagesSettings.UseDefaultEmailAccountForSendStoreOwnerEmails ? await _emailAccountService.GetEmailAccountByIdAsync(_emailAccountSettings.DefaultEmailAccountId) : null;
        storeOwnerEmailAccount ??= messageTemplateEmailAccount;

        return (storeOwnerEmailAccount.Email, storeOwnerEmailAccount.DisplayName);
    }

    /// 
    /// Get email and name to set ReplyTo property of email from customer 
    /// 
    /// Message template
    /// Customer
    /// Email address and name when reply to email
    protected virtual async Task<(string email, string name)> GetCustomerReplyToNameAndEmailAsync(MessageTemplate messageTemplate, Customer customer)
    {
        if (!messageTemplate.AllowDirectReply)
            return (null, null);

        var replyToEmail = await _customerService.IsGuestAsync(customer)
            ? string.Empty
            : customer.Email;

        var replyToName = await _customerService.IsGuestAsync(customer)
            ? string.Empty
            : await _customerService.GetCustomerFullNameAsync(customer);

        return (replyToEmail, replyToName);
    }

    /// 
    /// Get email and name to set ReplyTo property of email from order
    /// 
    /// Message template
    /// Order
    /// Email address and name when reply to email
    protected virtual async Task<(string email, string name)> GetCustomerReplyToNameAndEmailAsync(MessageTemplate messageTemplate, Order order)
    {
        if (!messageTemplate.AllowDirectReply)
            return (null, null);

        var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

        return (billingAddress.Email, $"{billingAddress.FirstName} {billingAddress.LastName}");
    }

    #endregion

    #region Methods

    #region Customer workflow

    /// 
    /// Sends 'New customer' notification message to a store owner
    /// 
    /// Customer instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendCustomerRegisteredStoreOwnerNotificationMessageAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CUSTOMER_REGISTERED_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a welcome message to a customer
    /// 
    /// Customer instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendCustomerWelcomeMessageAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CUSTOMER_WELCOME_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an email validation message to a customer
    /// 
    /// Customer instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendCustomerEmailValidationMessageAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CUSTOMER_EMAIL_VALIDATION_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an email re-validation message to a customer
    /// 
    /// Customer instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendCustomerEmailRevalidationMessageAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CUSTOMER_EMAIL_REVALIDATION_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            //email to re-validate
            var toEmail = customer.EmailToRevalidate;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends password recovery message to a customer
    /// 
    /// Customer instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendCustomerPasswordRecoveryMessageAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CUSTOMER_PASSWORD_RECOVERY_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends 'New request to delete customer' message to a store owner
    /// 
    /// Customer
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendDeleteCustomerRequestStoreOwnerNotificationAsync(Customer customer, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.DELETE_CUSTOMER_REQUEST_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    #endregion

    #region Order workflow

    /// 
    /// Sends an order placed notification to a vendor
    /// 
    /// Order instance
    /// Vendor instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPlacedVendorNotificationAsync(Order order, Vendor vendor, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        ArgumentNullException.ThrowIfNull(vendor);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PLACED_VENDOR_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId, vendor.Id);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = vendor.Email;
            var toName = vendor.Name;

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order placed notification to a store owner
    /// 
    /// Order instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPlacedStoreOwnerNotificationAsync(Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PLACED_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, order);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order placed notification to an affiliate
    /// 
    /// Order instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPlacedAffiliateNotificationAsync(Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var affiliate = await _affiliateService.GetAffiliateByIdAsync(order.AffiliateId);

        ArgumentNullException.ThrowIfNull(affiliate);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PLACED_AFFILIATE_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var affiliateAddress = await _addressService.GetAddressByIdAsync(affiliate.AddressId);
            var toEmail = affiliateAddress.Email;
            var toName = $"{affiliateAddress.FirstName} {affiliateAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order paid notification to a store owner
    /// 
    /// Order instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPaidStoreOwnerNotificationAsync(Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PAID_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, order);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order paid notification to an affiliate
    /// 
    /// Order instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPaidAffiliateNotificationAsync(Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var affiliate = await _affiliateService.GetAffiliateByIdAsync(order.AffiliateId);

        ArgumentNullException.ThrowIfNull(affiliate);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PAID_AFFILIATE_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var affiliateAddress = await _addressService.GetAddressByIdAsync(affiliate.AddressId);
            var toEmail = affiliateAddress.Email;
            var toName = $"{affiliateAddress.FirstName} {affiliateAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order paid notification to a customer
    /// 
    /// Order instance
    /// Message language identifier
    /// Attachment file path
    /// Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPaidCustomerNotificationAsync(Order order, int languageId,
        string attachmentFilePath = null, string attachmentFileName = null)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PAID_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                attachmentFilePath, attachmentFileName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order paid notification to a vendor
    /// 
    /// Order instance
    /// Vendor instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPaidVendorNotificationAsync(Order order, Vendor vendor, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        ArgumentNullException.ThrowIfNull(vendor);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PAID_VENDOR_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId, vendor.Id);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = vendor.Email;
            var toName = vendor.Name;

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order placed notification to a customer
    /// 
    /// Order instance
    /// Message language identifier
    /// Attachment file path
    /// Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderPlacedCustomerNotificationAsync(Order order, int languageId,
        string attachmentFilePath = null, string attachmentFileName = null)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PLACED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                attachmentFilePath, attachmentFileName);
        }).ToListAsync();
    }

    /// 
    /// Sends a shipment sent notification to a customer
    /// 
    /// Shipment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendShipmentSentCustomerNotificationAsync(Shipment shipment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(shipment);

        var order = await _orderService.GetOrderByIdAsync(shipment.OrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.SHIPMENT_SENT_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddShipmentTokensAsync(commonTokens, shipment, languageId);
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a shipment ready for pickup notification to a customer
    /// 
    /// Shipment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendShipmentReadyForPickupNotificationAsync(Shipment shipment, int languageId)
    {
        var order = await _orderService.GetOrderByIdAsync(shipment.OrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.SHIPMENT_READY_FOR_PICKUP_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddShipmentTokensAsync(commonTokens, shipment, languageId);
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a shipment delivered notification to a customer
    /// 
    /// Shipment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendShipmentDeliveredCustomerNotificationAsync(Shipment shipment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(shipment);

        var order = await _orderService.GetOrderByIdAsync(shipment.OrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.SHIPMENT_DELIVERED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddShipmentTokensAsync(commonTokens, shipment, languageId);
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order processing notification to a customer
    /// 
    /// Order instance
    /// Message language identifier
    /// Attachment file path
    /// Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderProcessingCustomerNotificationAsync(Order order, int languageId,
        string attachmentFilePath = null, string attachmentFileName = null)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_PROCESSING_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                attachmentFilePath, attachmentFileName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order completed notification to a customer
    /// 
    /// Order instance
    /// Message language identifier
    /// Attachment file path
    /// Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderCompletedCustomerNotificationAsync(Order order, int languageId,
        string attachmentFilePath = null, string attachmentFileName = null)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_COMPLETED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                attachmentFilePath, attachmentFileName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order cancelled notification to a customer
    /// 
    /// Order instance
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderCancelledCustomerNotificationAsync(Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_CANCELLED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order refunded notification to a store owner
    /// 
    /// Order instance
    /// Amount refunded
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderRefundedStoreOwnerNotificationAsync(Order order, decimal refundedAmount, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_REFUNDED_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddOrderRefundedTokensAsync(commonTokens, order, refundedAmount);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, order);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends an order refunded notification to a customer
    /// 
    /// Order instance
    /// Amount refunded
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendOrderRefundedCustomerNotificationAsync(Order order, decimal refundedAmount, int languageId)
    {
        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.ORDER_REFUNDED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddOrderRefundedTokensAsync(commonTokens, order, refundedAmount);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a new order note added notification to a customer
    /// 
    /// Order note
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewOrderNoteAddedCustomerNotificationAsync(OrderNote orderNote, int languageId)
    {
        ArgumentNullException.ThrowIfNull(orderNote);

        var order = await _orderService.GetOrderByIdAsync(orderNote.OrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_ORDER_NOTE_ADDED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderNoteTokensAsync(commonTokens, orderNote);
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "Recurring payment cancelled" notification to a store owner
    /// 
    /// Recurring payment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendRecurringPaymentCancelledStoreOwnerNotificationAsync(RecurringPayment recurringPayment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(recurringPayment);

        var order = await _orderService.GetOrderByIdAsync(recurringPayment.InitialOrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.RECURRING_PAYMENT_CANCELLED_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);
        await _messageTokenProvider.AddRecurringPaymentTokensAsync(commonTokens, recurringPayment);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, order);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "Recurring payment cancelled" notification to a customer
    /// 
    /// Recurring payment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendRecurringPaymentCancelledCustomerNotificationAsync(RecurringPayment recurringPayment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(recurringPayment);

        var order = await _orderService.GetOrderByIdAsync(recurringPayment.InitialOrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.RECURRING_PAYMENT_CANCELLED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);
        await _messageTokenProvider.AddRecurringPaymentTokensAsync(commonTokens, recurringPayment);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "Recurring payment failed" notification to a customer
    /// 
    /// Recurring payment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendRecurringPaymentFailedCustomerNotificationAsync(RecurringPayment recurringPayment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(recurringPayment);

        var order = await _orderService.GetOrderByIdAsync(recurringPayment.InitialOrderId) ?? throw new Exception("Order cannot be loaded");

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.RECURRING_PAYMENT_FAILED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, order.CustomerId);
        await _messageTokenProvider.AddRecurringPaymentTokensAsync(commonTokens, recurringPayment);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = billingAddress.Email;
            var toName = $"{billingAddress.FirstName} {billingAddress.LastName}";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    #endregion

    #region Newsletter workflow

    /// 
    /// Sends a newsletter subscription activation message
    /// 
    /// Newsletter subscription
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewsLetterSubscriptionActivationMessageAsync(NewsLetterSubscription subscription)
    {
        ArgumentNullException.ThrowIfNull(subscription);

        var store = await _storeContext.GetCurrentStoreAsync();
        var languageId = await EnsureLanguageIsActiveAsync(subscription.LanguageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEWSLETTER_SUBSCRIPTION_ACTIVATION_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddNewsLetterSubscriptionTokensAsync(commonTokens, subscription);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, subscription.Email, string.Empty);
        }).ToListAsync();
    }

    /// 
    /// Sends a newsletter subscription deactivation message
    /// 
    /// Newsletter subscription
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewsLetterSubscriptionDeactivationMessageAsync(NewsLetterSubscription subscription)
    {
        ArgumentNullException.ThrowIfNull(subscription);

        var store = await _storeContext.GetCurrentStoreAsync();
        var languageId = await EnsureLanguageIsActiveAsync(subscription.LanguageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEWSLETTER_SUBSCRIPTION_DEACTIVATION_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddNewsLetterSubscriptionTokensAsync(commonTokens, subscription);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, subscription.Email, string.Empty);
        }).ToListAsync();
    }

    #endregion

    #region Send a message to a friend

    /// 
    /// Sends "email a friend" message
    /// 
    /// Customer instance
    /// Message language identifier
    /// Product instance
    /// Customer's email
    /// Friend's email
    /// Personal message
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendProductEmailAFriendMessageAsync(Customer customer, int languageId,
        Product product, string customerEmail, string friendsEmail, string personalMessage)
    {
        ArgumentNullException.ThrowIfNull(customer);

        ArgumentNullException.ThrowIfNull(product);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.EMAIL_A_FRIEND_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        await _messageTokenProvider.AddProductTokensAsync(commonTokens, product, languageId);
        commonTokens.Add(new Token("EmailAFriend.PersonalMessage", personalMessage, true));
        commonTokens.Add(new Token("EmailAFriend.Email", customerEmail));

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, friendsEmail, string.Empty);
        }).ToListAsync();
    }

    /// 
    /// Sends wishlist "email a friend" message
    /// 
    /// Customer
    /// Message language identifier
    /// Customer's email
    /// Friend's email
    /// Personal message
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendWishlistEmailAFriendMessageAsync(Customer customer, int languageId,
        string customerEmail, string friendsEmail, string personalMessage)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.WISHLIST_TO_FRIEND_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        commonTokens.Add(new Token("Wishlist.PersonalMessage", personalMessage, true));
        commonTokens.Add(new Token("Wishlist.Email", customerEmail));

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, friendsEmail, string.Empty);
        }).ToListAsync();
    }

    #endregion

    #region Return requests

    /// 
    /// Sends 'New Return Request' message to a store owner
    /// 
    /// Return request
    /// Order item
    /// Order
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewReturnRequestStoreOwnerNotificationAsync(ReturnRequest returnRequest, OrderItem orderItem, Order order, int languageId)
    {
        ArgumentNullException.ThrowIfNull(returnRequest);

        ArgumentNullException.ThrowIfNull(orderItem);

        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_RETURN_REQUEST_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, returnRequest.CustomerId);
        await _messageTokenProvider.AddReturnRequestTokensAsync(commonTokens, returnRequest, orderItem, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, order);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends 'New Return Request' message to a customer
    /// 
    /// Return request
    /// Order item
    /// Order
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewReturnRequestCustomerNotificationAsync(ReturnRequest returnRequest, OrderItem orderItem, Order order)
    {
        ArgumentNullException.ThrowIfNull(returnRequest);

        ArgumentNullException.ThrowIfNull(orderItem);

        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        var languageId = await EnsureLanguageIsActiveAsync(order.CustomerLanguageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_RETURN_REQUEST_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var customer = await _customerService.GetCustomerByIdAsync(returnRequest.CustomerId);

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        await _messageTokenProvider.AddReturnRequestTokensAsync(commonTokens, returnRequest, orderItem, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = (await _customerService.IsGuestAsync(customer))
                ? billingAddress.Email
                : customer.Email;
            var toName = (await _customerService.IsGuestAsync(customer))
                ? billingAddress.FirstName
                : await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends 'Return Request status changed' message to a customer
    /// 
    /// Return request
    /// Order item
    /// Order
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendReturnRequestStatusChangedCustomerNotificationAsync(ReturnRequest returnRequest, OrderItem orderItem, Order order)
    {
        ArgumentNullException.ThrowIfNull(returnRequest);

        ArgumentNullException.ThrowIfNull(orderItem);

        ArgumentNullException.ThrowIfNull(order);

        var store = await _storeService.GetStoreByIdAsync(order.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        var languageId = await EnsureLanguageIsActiveAsync(order.CustomerLanguageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.RETURN_REQUEST_STATUS_CHANGED_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var customer = await _customerService.GetCustomerByIdAsync(returnRequest.CustomerId);

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddOrderTokensAsync(commonTokens, order, languageId);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        await _messageTokenProvider.AddReturnRequestTokensAsync(commonTokens, returnRequest, orderItem, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var billingAddress = await _addressService.GetAddressByIdAsync(order.BillingAddressId);

            var toEmail = (await _customerService.IsGuestAsync(customer))
                ? billingAddress.Email
                : customer.Email;
            var toName = (await _customerService.IsGuestAsync(customer))
                ? billingAddress.FirstName
                : await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    #endregion

    #region Forum Notifications

    /// 
    /// Sends a forum subscription message to a customer
    /// 
    /// Customer instance
    /// Forum Topic
    /// Forum
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewForumTopicMessageAsync(Customer customer, ForumTopic forumTopic, Forum forum, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_FORUM_TOPIC_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddForumTopicTokensAsync(commonTokens, forumTopic);
        await _messageTokenProvider.AddForumTokensAsync(commonTokens, forum);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a forum subscription message to a customer
    /// 
    /// Customer instance
    /// Forum post
    /// Forum Topic
    /// Forum
    /// Friendly (starts with 1) forum topic page to use for URL generation
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewForumPostMessageAsync(Customer customer, ForumPost forumPost, ForumTopic forumTopic,
        Forum forum, int friendlyForumTopicPageIndex, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_FORUM_POST_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddForumPostTokensAsync(commonTokens, forumPost);
        await _messageTokenProvider.AddForumTopicTokensAsync(commonTokens, forumTopic, friendlyForumTopicPageIndex, forumPost.Id);
        await _messageTokenProvider.AddForumTokensAsync(commonTokens, forum);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a private message notification
    /// 
    /// Private message
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendPrivateMessageNotificationAsync(PrivateMessage privateMessage, int languageId)
    {
        ArgumentNullException.ThrowIfNull(privateMessage);

        var store = await _storeService.GetStoreByIdAsync(privateMessage.StoreId) ?? await _storeContext.GetCurrentStoreAsync();

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.PRIVATE_MESSAGE_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddPrivateMessageTokensAsync(commonTokens, privateMessage);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, privateMessage.ToCustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var customer = await _customerService.GetCustomerByIdAsync(privateMessage.ToCustomerId);
            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    #endregion

    #region Misc

    /// 
    /// Sends 'New vendor account submitted' message to a store owner
    /// 
    /// Customer
    /// Vendor
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewVendorAccountApplyStoreOwnerNotificationAsync(Customer customer, Vendor vendor, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        ArgumentNullException.ThrowIfNull(vendor);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_VENDOR_ACCOUNT_APPLY_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        await _messageTokenProvider.AddVendorTokensAsync(commonTokens, vendor);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            var vendorAddress = await _addressService.GetAddressByIdAsync(vendor.AddressId);
            var replyToEmail = messageTemplate.AllowDirectReply ? vendorAddress.Email : "";
            var replyToName = messageTemplate.AllowDirectReply ? $"{vendorAddress.FirstName} {vendorAddress.LastName}" : "";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends 'Vendor information changed' message to a store owner
    /// 
    /// Vendor
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendVendorInformationChangeStoreOwnerNotificationAsync(Vendor vendor, int languageId)
    {
        ArgumentNullException.ThrowIfNull(vendor);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.VENDOR_INFORMATION_CHANGE_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddVendorTokensAsync(commonTokens, vendor);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            var vendorAddress = await _addressService.GetAddressByIdAsync(vendor.AddressId);
            var replyToEmail = messageTemplate.AllowDirectReply ? vendorAddress.Email : "";
            var replyToName = messageTemplate.AllowDirectReply ? $"{vendorAddress.FirstName} {vendorAddress.LastName}" : "";

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a gift card notification
    /// 
    /// Gift card
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendGiftCardNotificationAsync(GiftCard giftCard, int languageId)
    {
        ArgumentNullException.ThrowIfNull(giftCard);

        var order = await _orderService.GetOrderByOrderItemAsync(giftCard.PurchasedWithOrderItemId ?? 0);
        var currentStore = await _storeContext.GetCurrentStoreAsync();
        var store = order != null ? await _storeService.GetStoreByIdAsync(order.StoreId) ?? currentStore : currentStore;

        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.GIFT_CARD_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddGiftCardTokensAsync(commonTokens, giftCard, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = giftCard.RecipientEmail;
            var toName = giftCard.RecipientName;

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a product review notification message to a store owner
    /// 
    /// Product review
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendProductReviewStoreOwnerNotificationMessageAsync(ProductReview productReview, int languageId)
    {
        ArgumentNullException.ThrowIfNull(productReview);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.PRODUCT_REVIEW_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddProductReviewTokensAsync(commonTokens, productReview);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, productReview.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var customer = await _customerService.GetCustomerByIdAsync(productReview.CustomerId);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a product review reply notification message to a customer
    /// 
    /// Product review
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendProductReviewReplyCustomerNotificationMessageAsync(ProductReview productReview, int languageId)
    {
        ArgumentNullException.ThrowIfNull(productReview);

        var store = await _storeService.GetStoreByIdAsync(productReview.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.PRODUCT_REVIEW_REPLY_CUSTOMER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var customer = await _customerService.GetCustomerByIdAsync(productReview.CustomerId);

        //We should not send notifications to guests
        if (await _customerService.IsGuestAsync(customer))
            return new List();

        //We should not send notifications to guests
        if (await _customerService.IsGuestAsync(customer))
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddProductReviewTokensAsync(commonTokens, productReview);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "quantity below" notification to a store owner
    /// 
    /// Product
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendQuantityBelowStoreOwnerNotificationAsync(Product product, int languageId)
    {
        ArgumentNullException.ThrowIfNull(product);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.QUANTITY_BELOW_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var commonTokens = new List();
        await _messageTokenProvider.AddProductTokensAsync(commonTokens, product, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "quantity below" notification to a store owner
    /// 
    /// Attribute combination
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendQuantityBelowStoreOwnerNotificationAsync(ProductAttributeCombination combination, int languageId)
    {
        ArgumentNullException.ThrowIfNull(combination);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.QUANTITY_BELOW_ATTRIBUTE_COMBINATION_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var commonTokens = new List();
        var product = await _productService.GetProductByIdAsync(combination.ProductId);

        await _messageTokenProvider.AddProductTokensAsync(commonTokens, product, languageId);
        await _messageTokenProvider.AddAttributeCombinationTokensAsync(commonTokens, combination, languageId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends a "new VAT submitted" notification to a store owner
    /// 
    /// Customer
    /// Received VAT name
    /// Received VAT address
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewVatSubmittedStoreOwnerNotificationAsync(Customer customer,
        string vatName, string vatAddress, int languageId)
    {
        ArgumentNullException.ThrowIfNull(customer);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEW_VAT_SUBMITTED_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        commonTokens.Add(new Token("VatValidationResult.Name", vatName));
        commonTokens.Add(new Token("VatValidationResult.Address", vatAddress));

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a blog comment notification message to a store owner
    /// 
    /// Blog comment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the list of queued email identifiers
    /// 
    public virtual async Task> SendBlogCommentStoreOwnerNotificationMessageAsync(BlogComment blogComment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(blogComment);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.BLOG_COMMENT_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var customer = await _customerService.GetCustomerByIdAsync(blogComment.CustomerId);

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddBlogCommentTokensAsync(commonTokens, blogComment);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, blogComment.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a news comment notification message to a store owner
    /// 
    /// News comment
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendNewsCommentStoreOwnerNotificationMessageAsync(NewsComment newsComment, int languageId)
    {
        ArgumentNullException.ThrowIfNull(newsComment);

        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.NEWS_COMMENT_STORE_OWNER_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        var customer = await _customerService.GetCustomerByIdAsync(newsComment.CustomerId);

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddNewsCommentTokensAsync(commonTokens, newsComment);
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, newsComment.CustomerId);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var (toEmail, toName) = await GetStoreOwnerNameAndEmailAsync(emailAccount);
            var (replyToEmail, replyToName) = await GetCustomerReplyToNameAndEmailAsync(messageTemplate, customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName,
                replyToEmailAddress: replyToEmail, replyToName: replyToName);
        }).ToListAsync();
    }

    /// 
    /// Sends a 'Back in stock' notification message to a customer
    /// 
    /// Subscription
    /// Message language identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendBackInStockNotificationAsync(BackInStockSubscription subscription, int languageId)
    {
        ArgumentNullException.ThrowIfNull(subscription);

        var customer = await _customerService.GetCustomerByIdAsync(subscription.CustomerId);

        ArgumentNullException.ThrowIfNull(customer);

        //ensure that customer is registered (simple and fast way)
        if (!CommonHelper.IsValidEmail(customer.Email))
            return new List();

        var store = await _storeService.GetStoreByIdAsync(subscription.StoreId) ?? await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.BACK_IN_STOCK_NOTIFICATION, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List();
        await _messageTokenProvider.AddCustomerTokensAsync(commonTokens, customer);
        await _messageTokenProvider.AddBackInStockTokensAsync(commonTokens, subscription);

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            //event notification
            await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens);

            var toEmail = customer.Email;
            var toName = await _customerService.GetCustomerFullNameAsync(customer);

            return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName);
        }).ToListAsync();
    }

    /// 
    /// Sends "contact us" message
    /// 
    /// Message language identifier
    /// Sender email
    /// Sender name
    /// Email subject. Pass null if you want a message template subject to be used.
    /// Email body
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the queued email identifier
    /// 
    public virtual async Task> SendContactUsMessageAsync(int languageId, string senderEmail,
        string senderName, string subject, string body)
    {
        var store = await _storeContext.GetCurrentStoreAsync();
        languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id);

        var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CONTACT_US_MESSAGE, store.Id);
        if (!messageTemplates.Any())
            return new List();

        //tokens
        var commonTokens = new List
        {
            new("ContactUs.SenderEmail", senderEmail),
            new("ContactUs.SenderName", senderName)
        };

        return await messageTemplates.SelectAwait(async messageTemplate =>
        {
            //email account
            var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId);

            var tokens = new List(commonTokens);
            await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount);

            string fromEmail;
            string fromName;
            //required for some SMTP servers
            if (_commonSettings.UseSystemEmailForContactUsForm)
            {
                fromEmail = emailAccount.Email;
                fromName = emailAccount.DisplayName;
                body = $"From: {WebUtility.HtmlEncode(senderName)} - {WebUtility.HtmlEncode(senderEmail)}

{body}"; } else { fromEmail = senderEmail; fromName = senderName; } tokens.Add(new Token("ContactUs.Body", body, true)); //event notification await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens); var toEmail = emailAccount.Email; var toName = emailAccount.DisplayName; return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, fromEmail: fromEmail, fromName: fromName, subject: subject, replyToEmailAddress: senderEmail, replyToName: senderName); }).ToListAsync(); } /// /// Sends "contact vendor" message /// /// Vendor /// Message language identifier /// Sender email /// Sender name /// Email subject. Pass null if you want a message template subject to be used. /// Email body /// /// A task that represents the asynchronous operation /// The task result contains the queued email identifier /// public virtual async Task> SendContactVendorMessageAsync(Vendor vendor, int languageId, string senderEmail, string senderName, string subject, string body) { ArgumentNullException.ThrowIfNull(vendor); var store = await _storeContext.GetCurrentStoreAsync(); languageId = await EnsureLanguageIsActiveAsync(languageId, store.Id); var messageTemplates = await GetActiveMessageTemplatesAsync(MessageTemplateSystemNames.CONTACT_VENDOR_MESSAGE, store.Id); if (!messageTemplates.Any()) return new List(); //tokens var commonTokens = new List { new("ContactUs.SenderEmail", senderEmail), new("ContactUs.SenderName", senderName), new("ContactUs.Body", body, true) }; return await messageTemplates.SelectAwait(async messageTemplate => { //email account var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId); string fromEmail; string fromName; //required for some SMTP servers if (_commonSettings.UseSystemEmailForContactUsForm) { fromEmail = emailAccount.Email; fromName = emailAccount.DisplayName; body = $"From: {WebUtility.HtmlEncode(senderName)} - {WebUtility.HtmlEncode(senderEmail)}

{body}"; } else { fromEmail = senderEmail; fromName = senderName; } var tokens = new List(commonTokens); await _messageTokenProvider.AddStoreTokensAsync(tokens, store, emailAccount); //event notification await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens); var toEmail = vendor.Email; var toName = vendor.Name; return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, toEmail, toName, fromEmail: fromEmail, fromName: fromName, subject: subject, replyToEmailAddress: senderEmail, replyToName: senderName); }).ToListAsync(); } /// /// Sends a test email /// /// Message template identifier /// Send to email /// Tokens /// Message language identifier /// /// A task that represents the asynchronous operation /// The task result contains the queued email identifier /// public virtual async Task SendTestEmailAsync(int messageTemplateId, string sendToEmail, List tokens, int languageId) { var messageTemplate = await _messageTemplateService.GetMessageTemplateByIdAsync(messageTemplateId) ?? throw new ArgumentException("Template cannot be loaded"); //email account var emailAccount = await GetEmailAccountOfMessageTemplateAsync(messageTemplate, languageId); //event notification await _eventPublisher.MessageTokensAddedAsync(messageTemplate, tokens); //force sending messageTemplate.DelayBeforeSend = null; return await SendNotificationAsync(messageTemplate, emailAccount, languageId, tokens, sendToEmail, null); } #endregion #region Common /// /// Send notification /// /// Message template /// Email account /// Language identifier /// Tokens /// Recipient email address /// Recipient name /// Attachment file path /// Attachment file name /// "Reply to" email /// "Reply to" name /// Sender email. If specified, then it overrides passed "emailAccount" details /// Sender name. If specified, then it overrides passed "emailAccount" details /// Subject. If specified, then it overrides subject of a message template /// /// A task that represents the asynchronous operation /// The task result contains the queued email identifier /// public virtual async Task SendNotificationAsync(MessageTemplate messageTemplate, EmailAccount emailAccount, int languageId, IList tokens, string toEmailAddress, string toName, string attachmentFilePath = null, string attachmentFileName = null, string replyToEmailAddress = null, string replyToName = null, string fromEmail = null, string fromName = null, string subject = null) { ArgumentNullException.ThrowIfNull(messageTemplate); ArgumentNullException.ThrowIfNull(emailAccount); //retrieve localized message template data var bcc = await _localizationService.GetLocalizedAsync(messageTemplate, mt => mt.BccEmailAddresses, languageId); if (string.IsNullOrEmpty(subject)) subject = await _localizationService.GetLocalizedAsync(messageTemplate, mt => mt.Subject, languageId); var body = await _localizationService.GetLocalizedAsync(messageTemplate, mt => mt.Body, languageId); //Replace subject and body tokens var subjectReplaced = _tokenizer.Replace(subject, tokens, false); var bodyReplaced = _tokenizer.Replace(body, tokens, true); //limit name length toName = CommonHelper.EnsureMaximumLength(toName, 300); var email = new QueuedEmail { Priority = QueuedEmailPriority.High, From = !string.IsNullOrEmpty(fromEmail) ? fromEmail : emailAccount.Email, FromName = !string.IsNullOrEmpty(fromName) ? fromName : emailAccount.DisplayName, To = toEmailAddress, ToName = toName, ReplyTo = replyToEmailAddress, ReplyToName = replyToName, CC = string.Empty, Bcc = bcc, Subject = subjectReplaced, Body = bodyReplaced, AttachmentFilePath = attachmentFilePath, AttachmentFileName = attachmentFileName, AttachedDownloadId = messageTemplate.AttachedDownloadId, CreatedOnUtc = DateTime.UtcNow, EmailAccountId = emailAccount.Id, DontSendBeforeDateUtc = !messageTemplate.DelayBeforeSend.HasValue ? null : (DateTime?)(DateTime.UtcNow + TimeSpan.FromHours(messageTemplate.DelayPeriod.ToHours(messageTemplate.DelayBeforeSend.Value))) }; await _queuedEmailService.InsertQueuedEmailAsync(email); return email.Id; } #endregion #endregion }