protected override async Task ProcessEvent(object evt, CancellationToken cancellationToken)
    {
        if (evt is InvoiceEvent invoiceEvent)
        {
            var type = WebhookSender.GetWebhookEvent(invoiceEvent);
            if (type is null)
            {
                return;
            }

            var store = await _storeRepository.FindStore(invoiceEvent.Invoice.StoreId);


            var blob = store.GetStoreBlob();
            if (blob.EmailRules?.Any() is true)
            {
                var actionableRules = blob.EmailRules.Where(rule => rule.Trigger == type.Type).ToList();
                if (actionableRules.Any())
                {
                    var sender = await _emailSenderFactory.GetEmailSender(invoiceEvent.Invoice.StoreId);

                    foreach (UIStoresController.StoreEmailRule actionableRule in actionableRules)
                    {
                        var recipients = (actionableRule.To?.Split(",", StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty <string>())
                                         .Select(o =>
                        {
                            MailboxAddressValidator.TryParse(o, out var mb);
                            return(mb);
                        })
                                         .Where(o => o != null)
                                         .ToList();
                        if (actionableRule.CustomerEmail &&
                            MailboxAddressValidator.TryParse(invoiceEvent.Invoice.Metadata.BuyerEmail, out var bmb))
                        {
                            recipients.Add(bmb);
                        }
                        var i = GreenfieldInvoiceController.ToModel(invoiceEvent.Invoice, _linkGenerator, null);
                        sender.SendEmail(recipients.ToArray(), null, null, Interpolator(actionableRule.Subject, i),
                                         Interpolator(actionableRule.Body, i));
                    }
                }
            }
        }
    }
        public async Task <IActionResult> SendEmailFromStore(string storeId,
                                                             [FromBody] SendEmailRequest request)
        {
            var store = HttpContext.GetStoreData();

            if (store == null)
            {
                return(this.CreateAPIError(404, "store-not-found", "The store was not found"));
            }
            if (!MailboxAddressValidator.TryParse(request.Email, out var to))
            {
                ModelState.AddModelError(nameof(request.Email), "Invalid email");
                return(this.CreateValidationError(ModelState));
            }
            var emailSender = await _emailSenderFactory.GetEmailSender(storeId);

            if (emailSender is null)
            {
                return(this.CreateAPIError(404, "smtp-not-configured", "Store does not have an SMTP server configured."));
            }
            emailSender.SendEmail(to, request.Subject, request.Body);
            return(Ok());
        }
        async Task Notify(InvoiceEntity invoice, InvoiceEvent invoiceEvent, bool extendedNotification, bool sendMail)
        {
            var dto          = invoice.EntityToDTO();
            var notification = new InvoicePaymentNotificationEventWrapper()
            {
                Data = new InvoicePaymentNotification()
                {
                    Id              = dto.Id,
                    Currency        = dto.Currency,
                    CurrentTime     = dto.CurrentTime,
                    ExceptionStatus = dto.ExceptionStatus,
                    ExpirationTime  = dto.ExpirationTime,
                    InvoiceTime     = dto.InvoiceTime,
                    PosData         = dto.PosData,
                    Price           = dto.Price,
                    Status          = dto.Status,
                    BuyerFields     = invoice.RefundMail == null ? null : new Newtonsoft.Json.Linq.JObject()
                    {
                        new JProperty("buyerEmail", invoice.RefundMail)
                    },
                    PaymentSubtotals = dto.PaymentSubtotals,
                    PaymentTotals    = dto.PaymentTotals,
                    AmountPaid       = dto.AmountPaid,
                    ExchangeRates    = dto.ExchangeRates,
                    OrderId          = dto.OrderId
                },
                Event = new InvoicePaymentNotificationEvent()
                {
                    Code = (int)invoiceEvent.EventCode,
                    Name = invoiceEvent.Name
                },
                ExtendedNotification = extendedNotification,
                NotificationURL      = invoice.NotificationURL?.AbsoluteUri
            };

            // For lightning network payments, paid, confirmed and completed come all at once.
            // So despite the event is "paid" or "confirmed" the Status of the invoice is technically complete
            // This confuse loggers who think their endpoint get duplicated events
            // So here, we just override the status expressed by the notification
            if (invoiceEvent.Name == InvoiceEvent.Confirmed)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Confirmed);
            }
            if (invoiceEvent.Name == InvoiceEvent.PaidInFull)
            {
                notification.Data.Status = InvoiceState.ToString(InvoiceStatusLegacy.Paid);
            }
            //////////////////

            // We keep backward compatibility with bitpay by passing BTC info to the notification
            // we don't pass other info, as it is a bad idea to use IPN data for logic processing (can be faked)
            var btcCryptoInfo = dto.CryptoInfo.FirstOrDefault(c => c.GetpaymentMethodId() == new PaymentMethodId("BTC", Payments.PaymentTypes.BTCLike) && !string.IsNullOrEmpty(c.Address));

            if (btcCryptoInfo != null)
            {
#pragma warning disable CS0618
                notification.Data.Rate     = dto.Rate;
                notification.Data.Url      = dto.Url;
                notification.Data.BTCDue   = dto.BTCDue;
                notification.Data.BTCPaid  = dto.BTCPaid;
                notification.Data.BTCPrice = dto.BTCPrice;
#pragma warning restore CS0618
            }

            if (sendMail &&
                invoice.NotificationEmail is String e &&
                MailboxAddressValidator.TryParse(e, out MailboxAddress notificationEmail))
            {
                var json  = NBitcoin.JsonConverters.Serializer.ToString(notification);
                var store = await _StoreRepository.FindStore(invoice.StoreId);

                var storeName = store.StoreName ?? "BTCPay Server";
                var emailBody = $"Store: {storeName}<br>" +
                                $"Invoice ID: {notification.Data.Id}<br>" +
                                $"Status: {notification.Data.Status}<br>" +
                                $"Amount: {notification.Data.Price} {notification.Data.Currency}<br>" +
                                $"<br><details><summary>Details</summary><pre>{json}</pre></details>";

                (await _EmailSenderFactory.GetEmailSender(invoice.StoreId)).SendEmail(
                    notificationEmail,
                    $"{storeName} Invoice Notification - ${invoice.StoreId}",
                    emailBody);
            }

            if (invoice.NotificationURL != null)
            {
                _Queue.Enqueue(invoice.Id, (cancellationToken) => NotifyHttp(new ScheduledJob()
                {
                    TryCount = 0, Notification = notification
                }, cancellationToken));
            }
        }