public async Task <RunMergeTemplateProgress> RunMergeTemplateAsync( MergeTemplate mergeTemplate, Action <RunMergeTemplateProgress> progressReporter = null, CancellationToken cancellationToken = default(CancellationToken)) { if (mergeTemplate == null) { throw new ArgumentNullException(nameof(mergeTemplate)); } var emailMergeTemplate = mergeTemplate as EmailMergeTemplate; if (emailMergeTemplate == null) { throw new InvalidOperationException("Currently only mergeTemplates of type Email are supported."); } if (emailMergeTemplate.EmailTemplate == null) { throw new InvalidOperationException("MergeTemplate.EmailTemplate cannot ben null"); } string range = mergeTemplate.SheetName; var dataRange = await _sheetsService.GetDataRangeAsync( mergeTemplate.SpreadSheetId, mergeTemplate.SheetName); if (mergeTemplate.HeaderRowNumber > 1) { // we'll define a custom range range = new A1Notation(mergeTemplate.SheetName, dataRange.StartColumn, mergeTemplate.HeaderRowNumber, dataRange.EndColumn, dataRange.EndRow).ToString(); } if (!dataRange.EndRow.HasValue) { throw new InvalidOperationException($"Could not retrieve the number of rows in sheet {mergeTemplate.SheetName}"); } var progress = new RunMergeTemplateProgress( dataRange.EndRow.Value - (dataRange.StartRow ?? 1)); await _sheetsService.GetValuesAsDictionaryDataPump(mergeTemplate.SpreadSheetId, range, async values => { if (!ShouldProcess(mergeTemplate, values)) { progress.AddSkipped(); return; } try { await ProcessMergeTemplateAsync(emailMergeTemplate.EmailTemplate, values); progress.AddProcessed(); } catch (Exception err) { _logger.Error(err, "Error processing merge template"); progress.AddError(); } finally { progressReporter?.Invoke(progress); } }); return(progress); }