Skip to content

Commit

Permalink
Automatically remove duplicate recipients.
Browse files Browse the repository at this point in the history
Resolves #229
  • Loading branch information
Jericho committed May 5, 2018
1 parent a33711b commit b68965a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Source/StrongGrid.IntegrationTests/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,9 @@ private static async Task Mail(IClient client, TextWriter log, CancellationToken
{
new MailPersonalization
{
To = new[] { to1 },
To = new[] { to1, to1 },
Cc = new[] { to1 },
Bcc = new[] { to1 },
Substitutions = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("{{customer_type}}", "friend"),
Expand All @@ -190,6 +192,8 @@ private static async Task Mail(IClient client, TextWriter log, CancellationToken
new MailPersonalization
{
To = new[] { to2 },
Cc = new[] { to2, to2 },
Bcc = new[] { to2 },
Substitutions = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("{{customer_type}}", "customer"),
Expand Down
31 changes: 31 additions & 0 deletions Source/StrongGrid/Resources/Mail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ public async Task<string> SendAsync(
numberOfRecipients += personalizations.Sum(p => p?.Bcc?.Count(r => r != null) ?? 0);
if (numberOfRecipients >= 1000) throw new ArgumentOutOfRangeException("The total number of recipients must be less than 1000");

// SendGrid throws an unhelpful error when the Bcc email address is an empty string
if (string.IsNullOrWhiteSpace(mailSettings.Bcc.EmailAddress))
{
mailSettings.Bcc.EmailAddress = null;
}

var data = new JObject();
data.AddPropertyIfValue("from", from);
data.AddPropertyIfValue("reply_to", replyTo);
Expand All @@ -244,10 +250,35 @@ public async Task<string> SendAsync(
data.AddPropertyIfValue("mail_settings", mailSettings);
data.AddPropertyIfValue("tracking_settings", trackingSettings);

// This comparer is used to perform case-insensitive comparisons of email addresses
var emailAddressComparer = new LambdaComparer<MailAddress>((address1, address2) => address1.Email.Equals(address2.Email, StringComparison.OrdinalIgnoreCase));

// It's important to make a copy of the personalizations to ensure we don't modify the original array
var personalizationsCopy = personalizations.ToArray();
foreach (var personalization in personalizationsCopy)
{
// Avoid duplicate addresses. This is important because SendGrid does not throw any
// exception when a recipient is duplicated (which gives you the impression the email
// was sent) but it does not actually send the email
personalization.To = personalization.To?
.Distinct(emailAddressComparer)
.ToArray();
personalization.Cc = personalization.Cc?
.Distinct(emailAddressComparer)
.Except(personalization.To, emailAddressComparer)
.ToArray();
personalization.Bcc = personalization.Bcc?
.Distinct(emailAddressComparer)
.Except(personalization.To, emailAddressComparer)
.Except(personalization.Cc, emailAddressComparer)
.ToArray();

// SendGrid doesn't like empty arrays
if (!(personalization.To?.Any() ?? true)) personalization.To = null;
if (!(personalization.Cc?.Any() ?? true)) personalization.Cc = null;
if (!(personalization.Bcc?.Any() ?? true)) personalization.Bcc = null;

// Surround recipient names with double-quotes if necessary
personalization.To = EnsureRecipientsNamesAreQuoted(personalization.To);
personalization.Cc = EnsureRecipientsNamesAreQuoted(personalization.Cc);
personalization.Bcc = EnsureRecipientsNamesAreQuoted(personalization.Bcc);
Expand Down
42 changes: 42 additions & 0 deletions Source/StrongGrid/Utilities/LambdaComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;

namespace StrongGrid.Utilities
{
/// <summary>
/// LambdaComparer - avoids the need for writing custom IEqualityComparers
/// Usage:
/// List&lt;MyObject&gt; x = myCollection.Except(otherCollection, new LambdaComparer&lt;MyObject&gt;((x, y) => x.Id == y.Id)).ToList();
/// or
/// IEqualityComparer comparer = new LambdaComparer&lt;MyObject&gt;((x, y) => x.Id == y.Id);
/// List&lt;MyObject&gt; x = myCollection.Except(otherCollection, comparer).ToList();
/// </summary>
/// <typeparam name="T">The type to compare</typeparam>
/// <remarks>From: http://toreaurstad.blogspot.ca/2014/06/a-generic-iequalitycomparer-of-t.html</remarks>
internal class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;

public LambdaComparer(Func<T, T, bool> lambdaComparer)
: this(lambdaComparer, o => 0)
{
}

public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
{
_lambdaComparer = lambdaComparer ?? throw new ArgumentNullException(nameof(lambdaComparer));
_lambdaHash = lambdaHash ?? throw new ArgumentNullException(nameof(lambdaHash));
}

public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}

public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
}
}

0 comments on commit b68965a

Please sign in to comment.