danzuep / MailKitSimplified

Send and receive emails easily, fluently, with one line of code for each operation.
MIT License
79 stars 10 forks source link

Add an Exception template and check DefaultFrom usage #26

Closed danzuep closed 1 year ago

danzuep commented 1 year ago

Add an Action Exception template to the writer. The aim is to use the "await _writeEmail.Exception(ex).SendAsync();" method directly after having set the action for it in the builder.

Similarly, check the following:

Add DefaultFrom method missing in IEmailWriter interface. The aim is to use "await _writeEmail.To("test@localhost").SendAsync();" method directly without using the From method.

danzuep commented 1 year ago

@staoran would you want to look at doing this? The implementation would be similar to the custom authentication function.

staoran commented 1 year ago

I don't quite understand, the Exception(ex) method accepts a delegate? Used to handle exceptions?

staoran commented 1 year ago

However this made me think of a more flexible implementation, but more complex to implement. We can define an interface, and then inherit this interface to implement the sending logic. Then use it like this:

services.AddEmailWriter<MySend>();
or
services.AddEmailWriter<MySend>(options =>
        {
            options.SmtpHost = "smtp.example.com";
            options.SmtpPort = 587;
            options.SmtpCredential.UserName = "";
            //.......
        });

await _writeEmail.SendAsync<MySend>();
public class MySend : IWriter
{
    private readonly IServiceScope _serviceScope;
    private readonly MailSenderOptions _mailOptions;

    public MySend(IServiceScopeFactory scopeFactory)
    {
        _serviceScope = scopeFactory.CreateScope();
        _mailOptions = _serviceScope.ServiceProvider.GetRequiredService<IOptions<MailSenderOptions>>().Value;
    }

    public IEmailWriter Write(IEmailWriter emailWriter)
    {
            return emailWriter
                .From(_mailOptions.DefaultFrom, _mailOptions.DefaultFromName)
                .To(_mailOptions.DefaultTo)
                .Subject(_mailOptions.DefaultSubject)
                .BodyText(logMsg.Exception.ToString());
    }
}
staoran commented 1 year ago

Maybe this is a bit over-engineered, but it is really flexible and easy to reuse.

danzuep commented 1 year ago

Both good ideas. There's already an Action method, so now I've added AddEmailWriter too. While I was in that file I added AddScopedMailKitSimplifiedEmailSender so users can make their own scopes. I've also added an example of building an email from an exception, but it should be a custom exception class and needs to be thought through more so it's not in the interface. Test them out and feel free to get them working the way you'd like to use them in your branch and I'll take a look.

danzuep commented 1 year ago

Here's the existing way of setting and reusing default values:

var email = smtpSender.WriteEmail
    .From("from@example.com")
    .Subject("Hello World")
    .TryAttach("CompanyLogo.png");
await email.Copy().To("person1@example.com").SendAsync();
await email.Copy().To("person2@example.com").SendAsync();

Now I'm starting to think I should add a SaveAsDefault() method instead that stores EmailWriterOptions. This would negate the need for emailWriter.DefaultFrom as usage would be:

var email = smtpSender.WriteEmail
    .From("from@example.com")
    .Subject("Hello World")
    .TryAttach("CompanyLogo.png")
    .SaveAsDefault();
await email.To("person1@example.com").SendAsync();
await email.To("person2@example.com").SendAsync();
danzuep commented 1 year ago

If you think SaveAsDefault() is a good idea do you want to try implementing it? You could copy the email template model from MailKitSimplified.Generic or MailKitSimplified.Email.

staoran commented 1 year ago

I did a preliminary test, and MailboxAddress cannot be a configuration item.

Cannot create instance of type 'MimeKit.MailboxAddress' because it has multiple public parameterized constructors.

Maybe we need to add a new class to inherit it.

danzuep commented 1 year ago

This should help. I've added it and used it with SaveAsDefault, see what you think. Other properties like the email body could be added.

    public static class MimeMessageConverter
    {
        public static MimeMessage CopyAsTemplate(this MimeMessage original)
        {
            var copy = new MimeMessage();
            if (original.From.Count > 0)
                copy.From.AddRange(original.From);
            if (original.To.Count > 0)
                copy.To.AddRange(original.To);
            if (original.ReplyTo.Count > 0)
                copy.ReplyTo.AddRange(original.ReplyTo);
            if (original.Cc.Count > 0)
                copy.Cc.AddRange(original.Cc);
            if (original.Bcc.Count > 0)
                copy.Bcc.AddRange(original.Bcc);
            if (original.Sender != null)
                copy.Sender = original.Sender;
            copy.Subject = original.Subject;
            return copy;
        }
    }
danzuep commented 1 year ago

I did a preliminary test, and MailboxAddress cannot be a configuration item.

I see what you mean now, the configuration builder needs an empty constructor in order to be viable. A while back I made MailKitSimplified.Generic with something like this in mind, but there's a few things I want to change about it now that some time has passed. I have an uncommitted branch where I'm cleaning it up but in the meantime the existing generic email template in MailKitSimplified.Generic should work for this.

danzuep commented 1 year ago

The easiest way to achieve what you want would be to add a template file path option, then if it's not null use it like this: MimeKit.MimeMessage.Load(file.FullName);

Usage could be like:

var template = smtpSender.WriteEmail
    .From("from@example.com")
    .Subject("Hello World")
    .TryAttach("CompanyLogo.png")
    .SaveTemplateAsync(emlFilePath);
await template.To("person1@example.com").SendAsync();
var writer = await smtpSender.WithTemplateAsync(emlFilePath);
await writer.To("person2@example.com").SendAsync();

I've added this to a branch but haven't tested it yet.

staoran commented 1 year ago

Good morning. Thanks for your effort, I'll try it out. image Project restricted 8.0 syntax.

danzuep commented 1 year ago

Thanks for picking up on that, I've made DownloadAllAttachmentsAsync compatible with netstandard2.0 now, but still haven't tested it properly so let me know how it goes.

danzuep commented 1 year ago

I had a look yesterday and fixed a couple things, but looks like I made a syntax error right before pushing the changes. This works now (once I fix the last last-minute build syntax error):

var template = smtpSender.WriteEmail
    .From("from@example.com")
    .Subject("Hello World")
    .SaveTemplate();
await template.To("person1@example.com").SendAsync();
await template.To("person2@example.com").SendAsync();
await template.SaveTemplateAsync(emlFilePath);
var writer = await smtpSender.WithTemplateAsync(emlFilePath);
await writer.To("person3@example.com").SendAsync();
danzuep commented 1 year ago

I've merged what's there so far as it should all be usable, it needs more comments and unit tests but it'll do for now. Open a new ticket if you've got any other ideas :)