19Jan, 2026
Updating My Send Batch Upload Module to Handle Warnings Sent as Errors
I’m getting feedback from people out there that the Send Batch Upload and Merge module is saving them a lot of time, and that’s great! There is one thing I missed in the API’s behaviour, and you may have missed it to, and that’s the fact that some result data is being sent as an error.
A Deeper Dive Into the Issue
If you look at the Add Multiple Subscribers method with Send, you’ll be able to create or update up to 1,000 records at a time. The “Errors” string in the response will only indicate if emails are invalid.
Errors - the response error message that shows how many and which emails are invalid. It is null if all emails are valid.
This is not true. What will happen in reality is some emails will be skipped due to:
- Email is in the suppression list
- Suspicious email (admin@, bookkeeping@, etc.)
- If they’re unsubscribed in another email list.
The Errors string will look something like this:
1 items were ignored because Email is ignored because it's unsubscribed from all lists for recipients unsubscribed@nothing.com; 1 items were ignored because Email is in suppression list for recipients suppressed@nothing.com.
What this means is that, if you’re depending on the response’s status code to tell you something wasn’t accepted, you’d be wrong.
Parsing the Error String to Tally Changes in Your Uploads
Let’s go over the necessary changes to handle this situation. You can see this commit has everything to review.
Starting with the main task in Send.cs, I’ve added variables to count the changes.
var suspicious = 0;
var unsubscribed = 0;
var suppressed = 0;
There's some simple changes as the file is processed, which you can see in the commit itself. But where it gets really interesting is at the call to my Send gateway. Let's look at the changes here first:
var result = await sendGateway.AddMultipleSubscribersAsync(mailingListID, batchSubscribers);
if (result.Code == 0)
{
if (!string.IsNullOrWhiteSpace(result.Error))
{
try
{
var emailMatches = Regex.Matches(result.Error, @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}");
var emailsInError = emailMatches.Cast<Match>().Select(m => m.Value).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); foreach (var email in emailsInError)
{
Log.Info($"Removing {email} from totals counts.", this); addedEmails.RemoveAll(e => e.Equals(email, StringComparison.OrdinalIgnoreCase));
updatedEmails.RemoveAll(e => e.Equals(email, StringComparison.OrdinalIgnoreCase));
} var errorMessages = result.Error.ToLowerInvariant().Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (var errorMessage in errorMessages)
{
if (errorMessage.Contains("suspicious"))
{
suspicious += GetLeadingInt(errorMessage);
}
else if (errorMessage.Contains("unsubscribed"))
{
unsubscibed += GetLeadingInt(errorMessage);
}
else if (errorMessage.Contains("suppresion"))
{
suppressed += GetLeadingInt(errorMessage);
}
}
}
catch (Exception ex)
{
Log.Error($"Error processing API error message. This can be ignored but should be fixed. ({ex.Message})", this);
}
}
The call to the Send API is brought into a local variable called "result", and if it has content it's parsed into a list and checked for keywords related to the three conditions I've noted above. Later in the file these variables are referenced and shown to the User.
if (suspicious > 0)
{
message = $"{suspicious} subscribers were found as suspicious and were not processed.";
sb.AppendLine(message);
Log.Info(message, this);
} if (unsubscibed > 0)
{
message = $"{unsubscibed} subscribers were found as unsubscribed in another list and were not processed.";
sb.AppendLine(message);
Log.Info(message, this);
} if (suppressed > 0)
{
message = $"{suppressed} subscribers were found as suppressed and were not processed.";
sb.AppendLine(message);
Log.Info(message, this);
}
That's great, but the call to the Send API, through the gateway, was just getting a Boolean result. Changes had to be made so this task can get the Error string.
public async Task<ResponseBase> AddMultipleSubscribersAsync(string mailingListID, AddMultipleSubscribers addMultipleSubscribers)
{
if (addMultipleSubscribers == null
|| addMultipleSubscribers.Subscribers == null
|| addMultipleSubscribers.Subscribers.Count == 0)
return new ResponseBase()
{
Code = 1
};
Skipping a bunch here...
if (!response.IsSuccessStatusCode)
{
Log.Error($"Failed to post subscribers. Status code: {response.StatusCode}, Reason: {response.ReasonPhrase}", this);
return new ResponseBase()
{
Code = 1
};
} var jsonSerializerSettings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore
}; ResponseBase result = null; try
{
var responseContent = await response.Content.ReadAsStringAsync(); result = JsonConvert.DeserializeObject<ResponseBase>(responseContent, jsonSerializerSettings);
}
catch (Exception ex)
{
Log.Error($"Error deserializing subscribers.", ex, this);
throw;
} return result;
}
Above you can see we get that necessary Error string, making the context useful.
Receiving a Better Response
I’ve placed a feature request with Send, asking that counts of the above values are added to the response. I’m hoping this can be done so depending on working over a string isn’t necessary.


