Skip to content

gl-dotnet-email changes for Cc, Bcc, ReplyTo, and attachments #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions samples/GeekLearning.Email.Samples/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,33 @@ public IActionResult Index()

public async Task<IActionResult> SendEmail()
{
var user = new User

EmailAddress toAddress1 = new EmailAddress() { Email = "rhsmith@gworld.com", DisplayName = "Bob" };
EmailAddress toAddress2 = new EmailAddress() { Email = "sammy.davis@null.com", DisplayName = "Sam" };

// example of how to add a simple attachment. Add images, streams, etc as byte arrays, for example:

MimeKit.AttachmentCollection attachments = new MimeKit.AttachmentCollection
{
Email = "john@doe.me",
DisplayName = "John Doe"
{ "sample_attachment.txt", System.Text.Encoding.UTF8.GetBytes("This is the content of the file attachment.") }
};



await this.emailSender.SendEmailAsync(new EmailAddress() { Email="from.somebody@domain.tld", DisplayName="Me" }, "A simple message","This is a test message", attachments, toAddress1);


// Here is a second send example. No attachments, but using templates. Specifies to send a Cc to ccRecipient, using a decorator:

IEmailAddress ccRecipient = new EmailAddress() { Email = "myfriend@somewhere.com", DisplayName = "Joe Smith" };

var context = new
{
ApplicationName = "Email Sender Sample",
User = user
User = toAddress1
};
await this.emailSender.SendTemplatedEmailAsync("Invitation", context, toAddress2, ccRecipient.ToCc() );

await this.emailSender.SendTemplatedEmailAsync("Invitation", context, user);

return RedirectToAction("Index");
}
Expand Down
8 changes: 7 additions & 1 deletion src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@ public InMemoryEmailProvider(IEmailProviderOptions options, IInMemoryEmailReposi
{
this.inMemoryEmailRepository = inMemoryEmailRepository;
}


// for compatibility:
public Task SendEmailAsync(IEmailAddress from, IEnumerable<IEmailAddress> recipients, string subject, string text, string html)
{
return this.SendEmailAsync(from, recipients, subject, text, html, null);
}

public Task SendEmailAsync(IEmailAddress from, IEnumerable<IEmailAddress> recipients, string subject, string text, string html, MimeKit.AttachmentCollection attachments)
{
this.inMemoryEmailRepository.Save(new InMemoryEmail
{
Expand Down
3 changes: 2 additions & 1 deletion src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public async Task SendEmailAsync(
IEnumerable<IEmailAddress> recipients,
string subject,
string text,
string html)
string html,
MimeKit.AttachmentCollection attachments)
{

var client = new SendGridClient(this.apiKey);
Expand Down
43 changes: 38 additions & 5 deletions src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
namespace GeekLearning.Email.Smtp
{
using MailKit.Net.Smtp;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Internal;

public class SmtpEmailProvider : IEmailProvider
{
Expand Down Expand Up @@ -37,23 +38,55 @@ public async Task SendEmailAsync(
IEnumerable<IEmailAddress> recipients,
string subject,
string text,
string html)
string html,
AttachmentCollection attachments)
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress(from.DisplayName, from.Email));
foreach (var recipient in recipients)
{
message.To.Add(new MailboxAddress(recipient.DisplayName, recipient.Email));
InternetAddress address = new MailboxAddress(recipient.DisplayName, recipient.Email);
if (recipient is EmailAddressExt)
{
var recip = recipient as Internal.EmailAddressExt;
switch (recip.AddressAs)
{
case AddressTarget.Cc:
message.Cc.Add(address);
break;
case AddressTarget.Bcc:
message.Bcc.Add(address);
break;
case AddressTarget.ReplyTo:
message.ReplyTo.Add(address);
break;
default:
message.To.Add(address);
break;
}
}
else
{
message.To.Add(address);
}

}

message.Subject = subject;

var builder = new BodyBuilder
{
TextBody = text,
HtmlBody = html
HtmlBody = html,
};

builder.Attachments.Clear();
if (attachments != null)
{
foreach (var attachment in attachments)
{
builder.Attachments.Add(attachment);
}
}
message.Body = builder.ToMessageBody();

using (var client = new SmtpClient())
Expand Down
2 changes: 1 addition & 1 deletion src/GeekLearning.Email/IEmailAddress.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace GeekLearning.Email
namespace GeekLearning.Email
{
public interface IEmailAddress
{
Expand Down
13 changes: 10 additions & 3 deletions src/GeekLearning.Email/IEmailProvider.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
namespace GeekLearning.Email
namespace GeekLearning.Email
{
using System.Collections.Generic;
using System.Threading.Tasks;

using MimeKit;

public interface IEmailProvider
{
Task SendEmailAsync(IEmailAddress from, IEnumerable<IEmailAddress> recipients, string subject, string bodyText, string bodyHtml);
Task SendEmailAsync(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IEmailProvider should probably use AddressTarget

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was to be able to define a complete email transaction in one call. You might want to send to two (or more) different addresses, Cc: another, and Bcc: yet another, and also have a ReplyTo: for the message that is different that the From: address. (some hosting services required the From address to match the authentication user name, which might not be the same as the 'sender' of the immediate message). Using an attribute parameter for the email address implies a single target transaction. Slower if the app wants to send to several targets.

IEmailAddress from,
IEnumerable<IEmailAddress> recipients,
string subject,
string bodyText,
string bodyHtml
AttachmentCollection attachments=null);
}
}

8 changes: 8 additions & 0 deletions src/GeekLearning.Email/IEmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@

public interface IEmailSender
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be additional overloads using AddressTarget

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a thought. But there needs to be a solid way to link an AddressTarget to an email address. And how does it look if we have multiple email addresses in the call?

{
Task SendEmailAsync(string subject, string message, AttachmentCollection attachments, params IEmailAddress[] to);

Task SendEmailAsync(string subject, string message, params IEmailAddress[] to);

Task SendEmailAsync(IEmailAddress from, string subject, string message, params IEmailAddress[] to);

Task SendEmailAsync(IEmailAddress from, string subject, string message, AttachmentCollection attachments, params IEmailAddress[] to);

Task SendTemplatedEmailAsync<T>(string templateKey, T context, AttachmentCollection attachments, params IEmailAddress[] to);

Task SendTemplatedEmailAsync<T>(string templateKey, T context, params IEmailAddress[] to);

Task SendTemplatedEmailAsync<T>(IEmailAddress from, string templateKey, T context, params IEmailAddress[] to);

Task SendTemplatedEmailAsync<T>(IEmailAddress from, string templateKey, T context, AttachmentCollection attachments, params IEmailAddress[] to);
}
}
42 changes: 42 additions & 0 deletions src/GeekLearning.Email/Internal/EmailAddress.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
namespace GeekLearning.Email.Internal
{
public enum AddressTarget { Cc, Bcc, ReplyTo }

public class EmailAddress : IEmailAddress
{

public EmailAddress()
{
}
Expand All @@ -15,5 +18,44 @@ public EmailAddress(string email, string displayName)
public string Email { get; set; }

public string DisplayName { get; set; }

}

public class EmailAddressExt : IEmailAddress
{
private IEmailAddress _emailAddress {get; set;}

public EmailAddressExt(IEmailAddress emailAddress) : base() {
this._emailAddress = emailAddress;
}

public string Email {
get { return this._emailAddress.Email; }
}

public string DisplayName {
get { return this._emailAddress.DisplayName; }
}

public AddressTarget AddressAs { get; set; }
}

public static class EmailAddressExtensions
{

public static EmailAddressExt ToCc(this IEmailAddress emailAddress)
{
return new EmailAddressExt(emailAddress) { AddressAs = AddressTarget.Cc };
}

public static EmailAddressExt ToBcc(this IEmailAddress emailAddress)
{
return new EmailAddressExt(emailAddress) { AddressAs = AddressTarget.Bcc };
}

public static EmailAddressExt ToReplyTo(this IEmailAddress emailAddress)
{
return new EmailAddressExt(emailAddress) { AddressAs = AddressTarget.ReplyTo };
}
}
}
57 changes: 44 additions & 13 deletions src/GeekLearning.Email/Internal/EmailSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,34 @@ public EmailSender(
}
}

public Task SendEmailAsync(string subject, string message, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to)
{
return this.SendEmailAsync(options.DefaultSender, subject, message, attachments, to);
}

public Task SendEmailAsync(string subject, string message, params IEmailAddress[] to)
{
return this.SendEmailAsync(options.DefaultSender, subject, message, to);
return this.SendEmailAsync(options.DefaultSender, subject, message, null, to);
}

public Task SendEmailAsync(IEmailAddress from, string subject, string message, params IEmailAddress[] to)
{
return this.SendEmailAsync(from, subject, message, null, to);
}

public Task SendEmailAsync(IEmailAddress from, string subject, string message, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to)
{
return DoMockupAndSendEmailAsync(
from,
to,
subject,
message,
string.Format("<html><header></header><body>{0}</body></html>", message));
string.Format("<html><header></header><body>{0}</body></html>", message), attachments);
}

public Task SendTemplatedEmailAsync<T>(string templateKey, T context, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to)
{
return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, attachments, to);
}

public Task SendTemplatedEmailAsync<T>(string templateKey, T context, params IEmailAddress[] to)
Expand All @@ -74,7 +89,25 @@ await this.DoMockupAndSendEmailAsync(
to,
subjectTemplate.Apply(context),
textTemplate.Apply(context),
htmlTemplate.Apply(context));
htmlTemplate.Apply(context),
null
);
}

public async Task SendTemplatedEmailAsync<T>(IEmailAddress from, string templateKey, T context, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to)
{
var subjectTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.Subject);
var textTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.BodyText);
var htmlTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.BodyHtml);

await this.DoMockupAndSendEmailAsync(
from,
to,
subjectTemplate.Apply(context),
textTemplate.Apply(context),
htmlTemplate.Apply(context),
attachments
);
}

private Task<ITemplate> GetTemplateAsync(string templateKey, EmailTemplateType templateType)
Expand All @@ -84,10 +117,11 @@ private Task<ITemplate> GetTemplateAsync(string templateKey, EmailTemplateType t

private async Task DoMockupAndSendEmailAsync(
IEmailAddress from,
IEnumerable<IEmailAddress> recipients,
IEmailAddress [] recipients,
string subject,
string text,
string html)
string html,
MimeKit.AttachmentCollection attachments)
{
var finalRecipients = new List<IEmailAddress>();
var mockedUpRecipients = new List<IEmailAddress>();
Expand All @@ -96,15 +130,12 @@ private async Task DoMockupAndSendEmailAsync(
{
foreach (var recipient in recipients)
{
var emailParts = recipient.Email.Split('@');
if (emailParts.Length != 2)
{
throw new NotSupportedException("Bad recipient email.");
}

string trimmedEmail = recipient.Email.Trim();

var emailParts = trimmedEmail.Split('@');
var domain = emailParts[1];

if (!this.options.Mockup.Exceptions.Emails.Contains(recipient.Email)
if (!this.options.Mockup.Exceptions.Emails.Contains(trimmedEmail)
&& !this.options.Mockup.Exceptions.Domains.Contains(domain))
{
if (!mockedUpRecipients.Any())
Expand Down Expand Up @@ -142,7 +173,7 @@ await this.provider.SendEmailAsync(
finalRecipients,
subject,
text,
html);
html, attachments);
}
}
}
15 changes: 5 additions & 10 deletions tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,12 @@ public async Task SendNotification1(string storeName)
new SendGrid.SendGridEmailProviderType(),
};

var emailSender = new Internal.EmailSender(
providerTypes,
options,
this.storeFixture.Services.GetRequiredService<IStorageFactory>(),
this.storeFixture.Services.GetRequiredService<ITemplateLoaderFactory>());
IEmailAddress address = new Internal.EmailAddress() {
DisplayName = "test user",
Email = "no-reply@test.geeklearning.io"
};

await emailSender.SendTemplatedEmailAsync("Notification1", new { }, new Internal.EmailAddress
{
DisplayName = "test user",
Email = "no-reply@test.geeklearning.io"
});
await emailSender.SendTemplatedEmailAsync("Notification1", new { }, address);
}
}
}