public async Task <bool> OnMessageDispatchedAsync(string engagementAccount, string messageId, string customMessageId, List <string> targets, ConnectorIdentifier connector)
        {
            if (!string.IsNullOrEmpty(customMessageId))
            {
                // Insert id mapping
                await MessageIdMappingTableEntity.InsertOrMergeAsync(this.idMappingTable, new MessageIdMappingTableEntity(engagementAccount, messageId, customMessageId));
            }

            // Get record from summary table
            var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId);

            if (entity == null)
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for account={engagementAccount} messageId={messageId}");
                return(false);
            }

            // Calculating the unit according to provider
            var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName);

            var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata);

            if (entity.Units == null)
            {
                entity.Units          = units;
                entity.LastUpdateTime = DateTime.UtcNow;
                await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity);
            }

            return(true);
        }
        public async Task <bool> OnMessageReportUpdatedAsync(string messageId, string customMessageId, List <ReportDetail> reports, ConnectorIdentifier connector)
        {
            if (string.IsNullOrEmpty(messageId) && !string.IsNullOrEmpty(customMessageId))
            {
                // Get id mapping, retry 3 times if cannot found (in case racing with case #2 who has not insert the mapping yet)
                int retry = 3;
                while (retry > 0)
                {
                    messageId = (await MessageIdMappingTableEntity.GetAsync(idMappingTable, customMessageId))?.MessageId;
                    if (!string.IsNullOrEmpty(messageId))
                    {
                        break;
                    }

                    retry--;
                    await Task.Delay(1000);
                }

                if (string.IsNullOrEmpty(messageId))
                {
                    SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for customMessageId={customMessageId}");
                    return(false);
                }
            }

            // Get record from summary table
            var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId);

            if (entity == null)
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={messageId}");
                return(false);
            }

            // Calculating the unit according to provider
            // This is for long message that will be charged as multiple messages
            var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName);

            var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata);

            // Report might be ealier than dispatch result, so make sure units is updated
            if (entity.Units == null)
            {
                entity.Units          = units;
                entity.LastUpdateTime = DateTime.UtcNow;
                await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity);
            }

            // Update detail table
            if (reports != null && reports.Count > 0)
            {
                await this.UpdateMessageDetailsAsync(entity.EngagementAccount, entity, reports);
            }

            // Get account detail
            var account = await this.store.GetAccountAsync(entity.EngagementAccount);

            var succeed = reports.Where(r => r.State == MessageState.DELIVERED).Count();
            var failed  = reports.Where(r => r.State != MessageState.DELIVERED && r.State != MessageState.UNKNOWN).Count();

            // Push metric and billing usage
            if (Enum.TryParse(entity.MessageCategory, out MessageCategory category))
            {
                if (succeed > 0)
                {
                    this.metricManager.LogDeliverSuccess(succeed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category);

                    if (Constants.MessageCategoryToUsageTypeMappings.TryGetValue(category, out ResourceUsageType usageType))
                    {
                        var usage = new ResourceUsageRecord();
                        usage.EngagementAccount = entity.EngagementAccount;
                        usage.UsageType         = usageType;
                        usage.Quantity          = succeed * units;

                        await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> {
                            usage
                        }, CancellationToken.None);

                        SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={entity.EngagementAccount} usageType={usageType} quantity={succeed}x{units}");
                    }
                }

                if (failed > 0)
                {
                    this.metricManager.LogDeliverFailed(failed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category);
                }
            }
            else
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Failed, $"Failed to parse MessageCategory={entity.MessageCategory} MessageId={entity.MessageId}");
            }

            return(true);
        }