From 2ae944b56423af43f03b11ca62a095c0b28ed07b Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 14:24:30 -0700 Subject: [PATCH 01/17] Update EmailAddress.cs Adds property AddressAs, holding enum AddressTarget value. Used as a flag for the sending methods to specify the respective address into the corresponding fields in the SmtpClient. --- src/GeekLearning.Email/Internal/EmailAddress.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/GeekLearning.Email/Internal/EmailAddress.cs b/src/GeekLearning.Email/Internal/EmailAddress.cs index 2654f82..6f389d7 100644 --- a/src/GeekLearning.Email/Internal/EmailAddress.cs +++ b/src/GeekLearning.Email/Internal/EmailAddress.cs @@ -6,14 +6,17 @@ public EmailAddress() { } - public EmailAddress(string email, string displayName) + public EmailAddress(string email, string displayName, AddressTarget addressAs=AddressTarget.To) { this.Email = email; this.DisplayName = displayName; + this.AddressAs = addressAs; } public string Email { get; set; } public string DisplayName { get; set; } + + public AddressTarget AddressAs { get; set; } } } From 6a9a2fc4121c965a737d1357702d9622b379c786 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 14:43:42 -0700 Subject: [PATCH 02/17] Update EmailSender.cs --- .../Internal/EmailSender.cs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/GeekLearning.Email/Internal/EmailSender.cs b/src/GeekLearning.Email/Internal/EmailSender.cs index 5aa2895..af77996 100644 --- a/src/GeekLearning.Email/Internal/EmailSender.cs +++ b/src/GeekLearning.Email/Internal/EmailSender.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using Templating; + using System.Text.RegularExpressions; public class EmailSender : IEmailSender { @@ -43,27 +44,28 @@ public EmailSender( } } - public Task SendEmailAsync(string subject, string message, params IEmailAddress[] to) + public Task SendEmailAsync(string subject, string message, IEnumerable to, MimeKit.AttachmentCollection attachments) { - return this.SendEmailAsync(options.DefaultSender, subject, message, to); + return this.SendEmailAsync(options.DefaultSender, subject, message, to, attachments); } - public Task SendEmailAsync(IEmailAddress from, string subject, string message, params IEmailAddress[] to) + public Task SendEmailAsync(IEmailAddress from, string subject, string message, IEnumerable to, MimeKit.AttachmentCollection attachments) { return DoMockupAndSendEmailAsync( from, to, subject, message, - string.Format("
{0}", message)); + string.Format("
{0}", message), + attachments); } - public Task SendTemplatedEmailAsync(string templateKey, T context, params IEmailAddress[] to) + public Task SendTemplatedEmailAsync(string templateKey, T context, IEnumerable to, MimeKit.AttachmentCollection attachments) { - return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, to); + return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, to, attachments); } - public async Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, params IEmailAddress[] to) + public async Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, IEnumerable to, MimeKit.AttachmentCollection attachments) { var subjectTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.Subject); var textTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.BodyText); @@ -74,7 +76,8 @@ await this.DoMockupAndSendEmailAsync( to, subjectTemplate.Apply(context), textTemplate.Apply(context), - htmlTemplate.Apply(context)); + htmlTemplate.Apply(context), + attachments); } private Task GetTemplateAsync(string templateKey, EmailTemplateType templateType) @@ -87,7 +90,8 @@ private async Task DoMockupAndSendEmailAsync( IEnumerable recipients, string subject, string text, - string html) + string html, + MimeKit.AttachmentCollection attachments) { var finalRecipients = new List(); var mockedUpRecipients = new List(); @@ -96,12 +100,15 @@ private async Task DoMockupAndSendEmailAsync( { foreach (var recipient in recipients) { - var emailParts = recipient.Email.Split('@'); - if (emailParts.Length != 2) + + string trimmedEmail = recipient.Email.Trim(); + // not sure if it's the most friendly to validate in the sender method. Should be left to caller code, IMO + if (Regex.IsMatch(trimmedEmail, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase).Equals(false)) { throw new NotSupportedException("Bad recipient email."); } + var emailParts = trimmedEmail.Split('@'); var domain = emailParts[1]; if (!this.options.Mockup.Exceptions.Emails.Contains(recipient.Email) @@ -142,7 +149,7 @@ await this.provider.SendEmailAsync( finalRecipients, subject, text, - html); + html, attachments); } } } From 28b51172e11be48f4ac9b04227f66aa784f54120 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 14:47:23 -0700 Subject: [PATCH 03/17] Update IEmailAddress.cs Adds a property to IEmailAddress: AddressTarget AddressAs { get; } . Used by the Sender method to insert EmailAddress objects into the appropriate SendEmail client fields. --- src/GeekLearning.Email/IEmailAddress.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GeekLearning.Email/IEmailAddress.cs b/src/GeekLearning.Email/IEmailAddress.cs index 992d0de..929ce3d 100644 --- a/src/GeekLearning.Email/IEmailAddress.cs +++ b/src/GeekLearning.Email/IEmailAddress.cs @@ -1,9 +1,13 @@ -namespace GeekLearning.Email +namespace GeekLearning.Email { + public enum AddressTarget { From, To, Cc, Bcc, ReplyTo} + public interface IEmailAddress { string Email { get; } string DisplayName { get; } + + AddressTarget AddressAs { get; } } } From 52d1bcb51f1f0b50ab619eacfb50f779e0b70910 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 15:20:48 -0700 Subject: [PATCH 04/17] Update IEmailProvider Accommodates the attachments parameter. The param is specified as a default of null. This works, allowing the implementation to omit this, and it will default to null. Do not give the parameter an option of null in the implementation though, or it will always be null even if , and it will work. --- src/GeekLearning.Email/IEmailProvider.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/GeekLearning.Email/IEmailProvider.cs b/src/GeekLearning.Email/IEmailProvider.cs index a505895..9a5b231 100644 --- a/src/GeekLearning.Email/IEmailProvider.cs +++ b/src/GeekLearning.Email/IEmailProvider.cs @@ -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 recipients, string subject, string bodyText, string bodyHtml); + Task SendEmailAsync( + IEmailAddress from, + IEnumerable recipients, + string subject, + string bodyText, + string bodyHtml + AttachmentCollection attachments=null); } } From b241f9aed913c7b652724d41c5ac7d5347152c34 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 15:27:45 -0700 Subject: [PATCH 05/17] Update IEmailSender.cs Added attachment parameter, and IEnumerable for 'to' addresses. For consistency I changed 'to' parameter name to 'recipients' since my implementation of the EmailAddress class and its interface IEmailAddress includes more general purpose usage, with enum flags for Bcc, Cc, ReplyTo, and so on. --- src/GeekLearning.Email/IEmailSender.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GeekLearning.Email/IEmailSender.cs b/src/GeekLearning.Email/IEmailSender.cs index f5d0820..e5a5167 100644 --- a/src/GeekLearning.Email/IEmailSender.cs +++ b/src/GeekLearning.Email/IEmailSender.cs @@ -4,12 +4,12 @@ public interface IEmailSender { - Task SendEmailAsync(string subject, string message, params IEmailAddress[] to); + Task SendEmailAsync(string subject, string message, IEnumerable recipients, AttachmentCollection attachments=null); - Task SendEmailAsync(IEmailAddress from, string subject, string message, params IEmailAddress[] to); + Task SendEmailAsync(IEmailAddress from, string subject, string message, IEnumerable recipients, AttachmentCollection attachments=null); - Task SendTemplatedEmailAsync(string templateKey, T context, params IEmailAddress[] to); + Task SendTemplatedEmailAsync(string templateKey, T context, IEnumerable recipients, AttachmentCollection attachments=null); - Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, params IEmailAddress[] to); + Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, IEnumerable recipients, AttachmentCollection attachments=null); } } From 35d73314bce054020802665b56566754958ca96a Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 15:35:04 -0700 Subject: [PATCH 06/17] Update InMemoryEmailProvider.cs Basically this has changes for compatibility with the IEmailProvider interface. The attachments are accommodated but not implemented. I added a method wrapper so existing code should still work with the provider. --- src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs b/src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs index fea5443..c7f1953 100644 --- a/src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs +++ b/src/GeekLearning.Email.InMemory/InMemoryEmailProvider.cs @@ -12,8 +12,14 @@ public InMemoryEmailProvider(IEmailProviderOptions options, IInMemoryEmailReposi { this.inMemoryEmailRepository = inMemoryEmailRepository; } - + + // for compatibility: public Task SendEmailAsync(IEmailAddress from, IEnumerable recipients, string subject, string text, string html) + { + return this.SendEmailAsync(from, recipients, subject, text, html, null); + } + + public Task SendEmailAsync(IEmailAddress from, IEnumerable recipients, string subject, string text, string html, MimeKit.AttachmentCollection attachments) { this.inMemoryEmailRepository.Save(new InMemoryEmail { From 36618ae0ccb4395d9f7023e3fbb1703afcf4ef43 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 15:48:20 -0700 Subject: [PATCH 07/17] Update SmtpEmailProvider.cs Modifications for routing the new IEmailAddress objects to appropriate client fields (.To, .ReplyTo, etc). Also adds any attachment objects into the message object. --- .../SmtpEmailProvider.cs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs b/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs index f5ef0bb..f379edf 100644 --- a/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs +++ b/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs @@ -37,13 +37,29 @@ public async Task SendEmailAsync( IEnumerable 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); + switch (recipient.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; + } } message.Subject = subject; @@ -53,6 +69,15 @@ public async Task SendEmailAsync( TextBody = text, HtmlBody = html }; + + builder.Attachments.Clear(); + if (attachments != null) + { + foreach (var attachment in attachments) + { + builder.Attachments.Add(attachment); + } + } message.Body = builder.ToMessageBody(); From 08fb23e3f7c42a09d8e867ebe168de59ebbe5cef Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 15:55:42 -0700 Subject: [PATCH 08/17] Update SendGridEmailProvider.cs Basically just a compatibility change. The IEmailProvider interface wants the attachment parameter. I added it to the SendEmailAsync method for SendGrid, but it is not fully implemented. Also, since the interface sets a fallback to null, omitting it in calling code is allowed, and specifying attachments will have no effect. This might need some discussion. It is unclear to me whether Sendgrid discourages attachments anyway, so maybe it's better as is. Alternatively, the SendGrid methods could take up the attachment feature if desired. --- src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs b/src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs index 37da7d5..b7b8766 100644 --- a/src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs +++ b/src/GeekLearning.Email.SendGrid/SendGridEmailProvider.cs @@ -28,7 +28,8 @@ public async Task SendEmailAsync( IEnumerable recipients, string subject, string text, - string html) + string html, + MimeKit.AttachmentCollection attachments) { var client = new SendGridClient(this.apiKey); From d9198ab0b779d1fdcde04e4fa296928b35799ac6 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 16:00:11 -0700 Subject: [PATCH 09/17] Update HomeController.cs Modified the SendEmail Action to illustrate usage for the new implementations. --- .../Controllers/HomeController.cs | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs b/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs index 4218548..5a37e12 100644 --- a/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs +++ b/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs @@ -22,19 +22,37 @@ public IActionResult Index() public async Task SendEmail() { - var user = new User + // Email addresses have Email, DisplayName, and an enum: AddressTarget (To,Cc,Bcc, or ReplyTo) + List recipients = new List() { + new EmailAddress() { Email = "myfriend@aways.com", DisplayName = "Samuel", AddressAs = AddressTarget.To }, + new EmailAddress() { Email = "rhsmith@gworld.com", DisplayName = "Bob", AddressAs = AddressTarget.Cc }, + new EmailAddress() { Email = "igetit@world.gov", DisplayName="George Jones", AddressAs= AddressTarget.ReplyTo } + }; + + + // 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.") } }; + // the Reply-To addresses are simply another list of IEmailAddress objects, here, were are ignoring them as null. + // Also, in the From address, the AddressAs property is ignored, and the From address is positional and always treated as the From. + // Likewise, a From enumeration value in the recipients list is ignored, and will be treated as a To address. + await this.emailSender.SendEmailAsync(new EmailAddress() { Email="to.somebody@domain.tld", DisplayName="Me", AddressAs=AddressTarget.From }, "A simple message","This is a test message", recipients, attachments); + + + // Here is a second send example. No attachments, but using templates: + + recipients.Clear(); + recipients.Add(new EmailAddress { Email="george@alaska.edu", DisplayName="George Jones", AddressAs=AddressTarget.To }); var context = new { ApplicationName = "Email Sender Sample", - User = user + User = recipients }; - - await this.emailSender.SendTemplatedEmailAsync("Invitation", context, user); + await this.emailSender.SendTemplatedEmailAsync("Invitation", context, recipients ); return RedirectToAction("Index"); } From 2a1cf739c25425f5378d4ae9213ac3f6a7360902 Mon Sep 17 00:00:00 2001 From: Budsy Date: Tue, 17 Apr 2018 16:07:26 -0700 Subject: [PATCH 10/17] Update SendTemplatedTest.cs Slight modifications so that the solution will hopefully compile without fuss. Attachments not used for test. --- .../SendTemplatedTest.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs b/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs index 5ae7ec4..db64afe 100644 --- a/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs +++ b/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs @@ -55,17 +55,14 @@ public async Task SendNotification1(string storeName) new SendGrid.SendGridEmailProviderType(), }; - var emailSender = new Internal.EmailSender( - providerTypes, - options, - this.storeFixture.Services.GetRequiredService(), - this.storeFixture.Services.GetRequiredService()); + List address = new List() { + new Internal.EmailAddress(){ + DisplayName = "test user", + Email = "no-reply@test.geeklearning.io", + AddressAs = AddressTarget.To} + }; - await emailSender.SendTemplatedEmailAsync("Notification1", new { }, new Internal.EmailAddress - { - DisplayName = "test user", - Email = "no-reply@test.geeklearning.io" - }); + await emailSender.SendTemplatedEmailAsync("Notification1", new { }, address, null); } } } From 9635f2aa7ca90cd921dbb16676a8de4c0e4b5eda Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 13:38:12 -0700 Subject: [PATCH 11/17] Update EmailAddress.cs Extension methods for IEmailAddress for decorators .ToCc(), .ToBcc(), and .ReplyTo() . Attach to an IEmailAddress instance, optionally, to specify to the SendMailxxx() methods to which properties of the sendMail client to install an email address. --- .../Internal/EmailAddress.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/src/GeekLearning.Email/Internal/EmailAddress.cs b/src/GeekLearning.Email/Internal/EmailAddress.cs index 6f389d7..0ed5342 100644 --- a/src/GeekLearning.Email/Internal/EmailAddress.cs +++ b/src/GeekLearning.Email/Internal/EmailAddress.cs @@ -1,22 +1,61 @@ namespace GeekLearning.Email.Internal { + public enum AddressTarget { Cc, Bcc, ReplyTo } + public class EmailAddress : IEmailAddress { + public EmailAddress() { } - public EmailAddress(string email, string displayName, AddressTarget addressAs=AddressTarget.To) + public EmailAddress(string email, string displayName) { this.Email = email; this.DisplayName = displayName; - this.AddressAs = addressAs; } 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 }; + } + } } From 19bbf69f5643bd42b5f26d2e0fe4e2c0ffcacb02 Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 13:47:37 -0700 Subject: [PATCH 12/17] Update EmailSender.cs Attachments now can be set as parameters in overloaded methods. IEmailAddress objects now are params at the ends of method call parameter lists. --- .../Internal/EmailSender.cs | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/src/GeekLearning.Email/Internal/EmailSender.cs b/src/GeekLearning.Email/Internal/EmailSender.cs index af77996..d9e9d64 100644 --- a/src/GeekLearning.Email/Internal/EmailSender.cs +++ b/src/GeekLearning.Email/Internal/EmailSender.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; using Templating; - using System.Text.RegularExpressions; public class EmailSender : IEmailSender { @@ -44,28 +43,42 @@ public EmailSender( } } - public Task SendEmailAsync(string subject, string message, IEnumerable to, MimeKit.AttachmentCollection attachments) + public Task SendEmailAsync(string subject, string message, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to) { - return this.SendEmailAsync(options.DefaultSender, subject, message, to, attachments); + return this.SendEmailAsync(options.DefaultSender, subject, message, attachments, to); } - public Task SendEmailAsync(IEmailAddress from, string subject, string message, IEnumerable to, MimeKit.AttachmentCollection attachments) + public Task SendEmailAsync(string subject, string message, params IEmailAddress[] 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("
{0}", message), - attachments); + string.Format("
{0}", message), attachments); } - public Task SendTemplatedEmailAsync(string templateKey, T context, IEnumerable to, MimeKit.AttachmentCollection attachments) + public Task SendTemplatedEmailAsync(string templateKey, T context, MimeKit.AttachmentCollection attachments, params IEmailAddress[] to) { - return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, to, attachments); + return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, attachments, to); } - public async Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, IEnumerable to, MimeKit.AttachmentCollection attachments) + public Task SendTemplatedEmailAsync(string templateKey, T context, params IEmailAddress[] to) + { + return this.SendTemplatedEmailAsync(options.DefaultSender, templateKey, context, to); + } + + public async Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, params IEmailAddress[] to) { var subjectTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.Subject); var textTemplate = await this.GetTemplateAsync(templateKey, EmailTemplateType.BodyText); @@ -77,7 +90,24 @@ await this.DoMockupAndSendEmailAsync( subjectTemplate.Apply(context), textTemplate.Apply(context), htmlTemplate.Apply(context), - attachments); + null + ); + } + + public async Task SendTemplatedEmailAsync(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 GetTemplateAsync(string templateKey, EmailTemplateType templateType) @@ -87,7 +117,7 @@ private Task GetTemplateAsync(string templateKey, EmailTemplateType t private async Task DoMockupAndSendEmailAsync( IEmailAddress from, - IEnumerable recipients, + IEmailAddress [] recipients, string subject, string text, string html, @@ -100,18 +130,12 @@ private async Task DoMockupAndSendEmailAsync( { foreach (var recipient in recipients) { - string trimmedEmail = recipient.Email.Trim(); - // not sure if it's the most friendly to validate in the sender method. Should be left to caller code, IMO - if (Regex.IsMatch(trimmedEmail, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase).Equals(false)) - { - throw new NotSupportedException("Bad recipient email."); - } - + 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()) From 3d2e9897250273bbf5ca25795b3ff62dad8a6c4a Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 13:49:50 -0700 Subject: [PATCH 13/17] Update IEmailAddress.cs Interface for IEmailAddress restored to original, simple form. --- src/GeekLearning.Email/IEmailAddress.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/GeekLearning.Email/IEmailAddress.cs b/src/GeekLearning.Email/IEmailAddress.cs index 929ce3d..172477b 100644 --- a/src/GeekLearning.Email/IEmailAddress.cs +++ b/src/GeekLearning.Email/IEmailAddress.cs @@ -1,13 +1,9 @@ namespace GeekLearning.Email { - public enum AddressTarget { From, To, Cc, Bcc, ReplyTo} - public interface IEmailAddress { string Email { get; } string DisplayName { get; } - - AddressTarget AddressAs { get; } } } From b5dbc89b638c99a3b16bcdf8411732423422eeeb Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 13:55:55 -0700 Subject: [PATCH 14/17] Update IEmailSender.cs Overloaded methods now specified in the interface. Allows attachments in separate methods. IEmailAddress objects now params . --- src/GeekLearning.Email/IEmailSender.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/GeekLearning.Email/IEmailSender.cs b/src/GeekLearning.Email/IEmailSender.cs index e5a5167..664c9d6 100644 --- a/src/GeekLearning.Email/IEmailSender.cs +++ b/src/GeekLearning.Email/IEmailSender.cs @@ -4,12 +4,20 @@ public interface IEmailSender { - Task SendEmailAsync(string subject, string message, IEnumerable recipients, AttachmentCollection attachments=null); + 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, IEnumerable recipients, AttachmentCollection attachments=null); + Task SendEmailAsync(IEmailAddress from, string subject, string message, params IEmailAddress[] to); - Task SendTemplatedEmailAsync(string templateKey, T context, IEnumerable recipients, AttachmentCollection attachments=null); + Task SendEmailAsync(IEmailAddress from, string subject, string message, AttachmentCollection attachments, params IEmailAddress[] to); - Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, IEnumerable recipients, AttachmentCollection attachments=null); + Task SendTemplatedEmailAsync(string templateKey, T context, AttachmentCollection attachments, params IEmailAddress[] to); + + Task SendTemplatedEmailAsync(string templateKey, T context, params IEmailAddress[] to); + + Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, params IEmailAddress[] to); + + Task SendTemplatedEmailAsync(IEmailAddress from, string templateKey, T context, AttachmentCollection attachments, params IEmailAddress[] to); } } From 2e4b9e19a907cb3003e092ca2f1d6153756f6c41 Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 14:00:31 -0700 Subject: [PATCH 15/17] Update SmtpEmailProvider.cs Uses optional decorators on IEmailAddress objects. Plus, email message attachments. --- .../SmtpEmailProvider.cs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs b/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs index f379edf..c9f269e 100644 --- a/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs +++ b/src/GeekLearning.Email.Smtp/SmtpEmailProvider.cs @@ -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 { @@ -45,21 +46,30 @@ public async Task SendEmailAsync( foreach (var recipient in recipients) { InternetAddress address = new MailboxAddress(recipient.DisplayName, recipient.Email); - switch (recipient.AddressAs) + if (recipient is EmailAddressExt) { - 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; + 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; @@ -67,9 +77,8 @@ public async Task SendEmailAsync( var builder = new BodyBuilder { TextBody = text, - HtmlBody = html + HtmlBody = html, }; - builder.Attachments.Clear(); if (attachments != null) { @@ -78,7 +87,6 @@ public async Task SendEmailAsync( builder.Attachments.Add(attachment); } } - message.Body = builder.ToMessageBody(); using (var client = new SmtpClient()) From 90d4f8d117414c99e7c87b4d38d589552f1feb06 Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 14:11:23 -0700 Subject: [PATCH 16/17] Update SendTemplatedTest.cs Conforms to changes. --- .../SendTemplatedTest.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs b/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs index db64afe..5fb3bc7 100644 --- a/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs +++ b/tests/GeekLearning.Email.Integration.Test/SendTemplatedTest.cs @@ -55,14 +55,12 @@ public async Task SendNotification1(string storeName) new SendGrid.SendGridEmailProviderType(), }; - List address = new List() { - new Internal.EmailAddress(){ + IEmailAddress address = new Internal.EmailAddress() { DisplayName = "test user", - Email = "no-reply@test.geeklearning.io", - AddressAs = AddressTarget.To} + Email = "no-reply@test.geeklearning.io" }; - await emailSender.SendTemplatedEmailAsync("Notification1", new { }, address, null); + await emailSender.SendTemplatedEmailAsync("Notification1", new { }, address); } } } From 2493e334758fcc1bed0cb9d12e8d9e6f4c5bf438 Mon Sep 17 00:00:00 2001 From: Budsy Date: Thu, 19 Apr 2018 14:25:36 -0700 Subject: [PATCH 17/17] Update HomeController.cs Updated SendMail action method to show usage of sending mail with an attachment, and a second send demonstrating adding a Cc 'to' address, using the optional decorators. --- .../Controllers/HomeController.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs b/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs index 5a37e12..8f8544a 100644 --- a/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs +++ b/samples/GeekLearning.Email.Samples/Controllers/HomeController.cs @@ -22,13 +22,9 @@ public IActionResult Index() public async Task SendEmail() { - // Email addresses have Email, DisplayName, and an enum: AddressTarget (To,Cc,Bcc, or ReplyTo) - List recipients = new List() { - new EmailAddress() { Email = "myfriend@aways.com", DisplayName = "Samuel", AddressAs = AddressTarget.To }, - new EmailAddress() { Email = "rhsmith@gworld.com", DisplayName = "Bob", AddressAs = AddressTarget.Cc }, - new EmailAddress() { Email = "igetit@world.gov", DisplayName="George Jones", AddressAs= AddressTarget.ReplyTo } - }; - + + 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: @@ -37,22 +33,22 @@ public async Task SendEmail() { "sample_attachment.txt", System.Text.Encoding.UTF8.GetBytes("This is the content of the file attachment.") } }; - // the Reply-To addresses are simply another list of IEmailAddress objects, here, were are ignoring them as null. - // Also, in the From address, the AddressAs property is ignored, and the From address is positional and always treated as the From. - // Likewise, a From enumeration value in the recipients list is ignored, and will be treated as a To address. - await this.emailSender.SendEmailAsync(new EmailAddress() { Email="to.somebody@domain.tld", DisplayName="Me", AddressAs=AddressTarget.From }, "A simple message","This is a test message", recipients, attachments); - // Here is a second send example. No attachments, but using templates: + 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" }; - recipients.Clear(); - recipients.Add(new EmailAddress { Email="george@alaska.edu", DisplayName="George Jones", AddressAs=AddressTarget.To }); var context = new { ApplicationName = "Email Sender Sample", - User = recipients + User = toAddress1 }; - await this.emailSender.SendTemplatedEmailAsync("Invitation", context, recipients ); + await this.emailSender.SendTemplatedEmailAsync("Invitation", context, toAddress2, ccRecipient.ToCc() ); + return RedirectToAction("Index"); }