Code Monkey home page Code Monkey logo

mailkitsimplified's Introduction

MailKitSimplified Code Size

Sending and receiving emails sounds simple, after all, electronic mail existed decades before the Internet. If you're looking for an all-in-one .NET solution for email, you'll quickly discover MailKit is recommended by even the likes of Microsoft due to how it implements the RFC standard. Unfortunately the downside of doing it all is that MailKit can be difficult to set up and use, especially the first time you go to try something like working with attachments or writing a reply. The aim of this package is to make sending and receiving emails as simple as possible!

SMTP with MailKitSimplified.Sender NuGet Downloads

Sending an email with MailKitSimplified.Sender is as easy as:

using var smtpSender = SmtpSender.Create("localhost");
await smtpSender.WriteEmail.To("test@localhost").SendAsync();

IMAP with MailKitSimplified.Receiver NuGet Downloads

Receiving emails with MailKitSimplified.Receiver is as easy as:

using var imapReceiver = ImapReceiver.Create("localhost");
var mimeMessages = await imapReceiver.ReadMail.Top(1).GetMimeMessagesAsync();

You can even monitor an email folder for new messages asynchronously, never before has it been this easy!

await imapReceiver.MonitorFolder.OnMessageArrival(m => Console.WriteLine(m.UniqueId)).IdleAsync();

Once you've got either a mime message or a message summary, replying is now equally as intuitive.

var mimeReply = mimeMessage.GetReplyMessage("<p>Reply here.</p>").From("[email protected]");

You're welcome. ๐Ÿฅฒ

Example Usage Development Release

The examples above will actually work with no other setup if you use something like smtp4dev (e.g. docker run -d -p 3000:80 -p 25:25 -p 143:143 rnwood/smtp4dev), but below are some more realistic examples. The cancellation token is recommended but not required.

Sending Mail

using var smtpSender = SmtpSender.Create("smtp.example.com:587")
    .SetCredential("[email protected]", "App1icati0nP455w0rd")
    .SetProtocolLog("Logs/SmtpClient.txt");
await smtpSender.WriteEmail
    .From("[email protected]")
    .To("[email protected]")
    .Bcc("[email protected]")
    .Subject("Hello World")
    .BodyHtml("<p>Hi</p>")
    .Attach("appsettings.json")
    .TryAttach(@"Logs\ImapClient.txt")
    .SendAsync(cancellationToken);

See the MailKitSimplified.Sender wiki for more information.

Receiving Mail

using var imapReceiver = ImapReceiver.Create("imap.example.com:993")
    .SetCredential("[email protected]", "App1icati0nP455w0rd")
    .SetProtocolLog("Logs/ImapClient.txt")
    .SetFolder("INBOX");
var mimeMessages = await imapReceiver.ReadMail.Top(10)
    .GetMimeMessagesAsync(cancellationToken);

To only download the message parts you want to use:

var reader = imapReceiver.ReadMail
    .Skip(0).Take(250, continuous: true)
    .Items(MessageSummaryItems.Envelope);
var messageSummaries = await reader.GetMessageSummariesAsync(cancellationToken);

Note: MailKit returns results in ascending order by default, use messageSummaries.Reverse() to get descending results or imapReceiver.ReadMail.Top(#) to get the newest results.

To query unread emails from the IMAP server:

var messageSummaries = await imapReceiver.ReadFrom("INBOX/Subfolder")
    .Query(SearchQuery.NotSeen)
    .ItemsForMimeMessages()
    .GetMessageSummariesAsync(cancellationToken);

To asynchronously monitor the mail folder for incoming messages:

await imapReceiver.MonitorFolder
    .SetMessageSummaryItems()
    .SetIgnoreExistingMailOnConnect()
    .OnMessageArrival(OnArrivalAsync)
    .IdleAsync(cancellationToken);

To asynchronously forward or reply to message summaries as they arrive:

async Task OnArrivalAsync(IMessageSummary messageSummary)
{
    var mimeForward = await messageSummary.GetForwardMessageAsync(
        "<p>FYI.</p>", includeMessageId: true);
    mimeForward.From.Add("[email protected]");
    mimeForward.To.Add("[email protected]");
    //logger.LogInformation($"Reply: \r\n{mimeForward.HtmlBody}");
    //await smtpSender.SendAsync(mimeForward, cancellationToken);
}

See the MailKitSimplified.Receiver wiki for more information.

See Also License

Examples of things like dependency injection, a hosted service, or an ASP.NET API can also be found in the GitHub samples.

mailkitsimplified's People

Contributors

danzuep avatar dependabot[bot] avatar osamaalrashed avatar staoran avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

mailkitsimplified's Issues

IMAP Oauth2 for O365

Hey Daniel - do you plan to support oauth?
Right now I only see options to pass username/password with the .SetCredentials helper.

var imapReceiver = ImapReceiver.Create(mailbox.IMAPServer + ":" + mailbox.IMAPPort)
                    .SetCredential(mailbox.IMAPUsername, mailbox.IMAPPassword)
                    .SetFolder(folder);

O365 IMAP now requires OAUTH - and mailkit does support it:

var confidentialClientApplication = ConfidentialClientApplicationBuilder
                .Create(mailbox.O365ClientId)
                .WithClientSecret(mailbox.O365ClientSecret)
                .WithAuthority(new Uri("https://login.microsoftonline.com/" + mailbox.O365TenantId + "/v2.0"))
                .Build();

      var authenticationResult = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync();

      var authToken = authenticationResult;
      var oauth2 = new SaslMechanismOAuth2(mailbox.Email, authToken.AccessToken);

      using (var client = new ImapClient(new ProtocolLogger("imapLog.txt")))
      {
          client.Connect("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect);
          client.Authenticate(oauth2);
          var inbox = client.Inbox;
          inbox.Open(MailKit.FolderAccess.ReadOnly);
          Console.WriteLine("Total messages: {0}", inbox.Count);
          Console.WriteLine("Recent messages: {0}", inbox.Recent);
          client.Disconnect(true);

          Assert.True(inbox.Count > 0);
      }

Can I offer a pull request?

Thanks,
Max

MailKit Dependency Injection With Quartz

Dear Daniel,

I am injecting the MailKitSimplifiedEmailReceiver in QuartzHostedService and using it in Quartz job, but due to following error Quartz job is not firing again, also the log is not being written:

warn: MailKitSimplified.Receiver.Services.LogFileWriterQueue[0]
Log queue cancelled while logs are still being written.
fail: Quartz.Core.QuartzSchedulerThread[0]
Runtime error occurred in main trigger firing loop.
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'LoggerFactory'.
at Microsoft.Extensions.Logging.LoggerFactory.CreateLogger(String categoryName)
at Quartz.Simpl.MicrosoftLoggingProvider.GetLogger(String name)
at Quartz.Logging.LogProvider.GetLogger(String name)
at Quartz.Logging.LogProvider.GetLogger(Type type, String fallbackTypeName)
at Quartz.Core.JobRunShell..ctor(IScheduler scheduler, TriggerFiredBundle bundle)
at Quartz.Impl.StdJobRunShellFactory.CreateJobRunShell(TriggerFiredBundle bndle)
at Quartz.Core.QuartzSchedulerThread.Run()

Arithmetic Operation Overflow

Dear Daniel,

I am facing a arithmetic overflow issue while fetching the message summaries. Following are the details:

Exception:
System.OverflowException: 'Arithmetic operation resulted in an overflow.'

Code:
// Build uuid range to fetch
UniqueIdRange? range = new UniqueIdRange(new UniqueId((uint)startUID), UniqueId.MaxValue);

// Serach inbox for messages uid
IList? messageSummaries = await _mailFolderReader.GetMessageSummariesAsync(range, MessageSummaryItems.UniqueId | MessageSummaryItems.GMailThreadId | MessageSummaryItems.InternalDate | MessageSummaryItems.PreviewText | MessageSummaryItems.Envelope);

How to connect STARTTLS enabled server?

How can convert this configuration for ImapReceiver

            var client = new ImapClient();
            client.CheckCertificateRevocation = false;
            client.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
            client.ServerCertificateValidationCallback = (s, c, h, e) => true;
            client.Connect(item.IMAPHost, item.IMAPPort, MailKit.Security.SecureSocketOptions.StartTls);
            client.Authenticate(item.Email, item.IMAPPassword);

.net Core MVC Dependency Injection Issue

Dear Daniel,

I am facing following error when i am added dependency injection in my .net Core MVC appliation:

ObjectDisposedException: Cannot access a disposed object. Object name: 'LoggerFactory'.

Microsoft.Extensions.Logging.LoggerFactory.CreateLogger(string categoryName)
Microsoft.AspNetCore.Authentication.AuthenticationHandler<TOptions>..ctor(IOptionsMonitor<TOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler..ctor(IOptionsMonitor<OpenIdConnectOptions> options, ILoggerFactory logger, HtmlEncoder htmlEncoder, UrlEncoder encoder, ISystemClock clock)
InvokeStub_OpenIdConnectHandler..ctor(object , object , IntPtr* )
System.Reflection.ConstructorInvoker.Invoke(object obj, IntPtr* args, BindingFlags invokeAttr)
System.Reflection.RuntimeConstructorInfo.InvokeWithManyArguments(RuntimeConstructorInfo ci, int argCount, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor<TArgument, TResult>.VisitCallSite(ServiceCallSite callSite, TArgument argument)
Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine+<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
Microsoft.AspNetCore.Authentication.AuthenticationHandlerProvider.GetHandlerAsync(HttpContext context, string authenticationScheme)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|8_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.HandleException(HttpContext context, ExceptionDispatchInfo edi)
Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|8_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

How can I see conversations in Mail?

Hello,

I want to capture all conversations of an email as a list, can we do this?

What I want to do is to create a view, a user interface, that shows all conversations in an email history.

Thanks.

Monitor multiple emails

Hello!
How i can monitor inbox folders from more than 1 email (for example, 5)?
And how i can configure it in appsettings.json?

can support a dynamic template?

If I have a template but part of the content changes dynamically, can I support the rendering of dynamic templates? I think this feature can be added.

Storing mail in Draft folder

Kudos to your great library - makes it super easy to consume! ๐Ÿ‘

Do you plan to add an API to store messages in folders? In my case I want to store them in the DRAFT folder.

Cheers and thanks,
Max

ReplyTo Issue

Dear,

I am constructing reply email as following:

MimeMessage replyMessage = await messageSummary.GetReplyMessageAsync(emailContent, true, true, true);
await _smtpSender.SendAsync(replyMessage);

But some how when i am receiving the email in my Outlook and try to reply, somehow it adds an extra email address in "To", which is "noreply@localhost". Can you highlight where i am going wrong.

Also the real requirement that in reply email content i need to add all of the previous email bodies as you can see in Outlook.

Download Mime Message without Attachment

Hi Daniel,

I am downloading the single mime message with the following code:

// Fetch email message against presisted uid
MimeMessage mimeMessage = await _mailFolderReader.GetMimeMessageAsync(new UniqueId(uniqueId), cancellationToken);

Can you please guide how can I download mime message without attachments. I want to download attachment later on demand.

Special Folder Monitoring

Dear,

I need to monitor IMAP folder "ALL" for Gmail IMAP. I am passing "ALL" in SetFolder function but it is throwing exception that "Folder not found".
Actually i need to monitor Gmail "Inbox" and "Sent" folder because i have to fetch full conversations "Received & Replied".

Add an Exception template and check DefaultFrom usage

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.

SecureSocketOptions are not transmitted to ConnectAsync method

Hello!
I have a problem: mailkit.net.imap.imapprotocolexception the imap server has unexpectedly disconnected.
And i found a solution on stackoverflow: https://stackoverflow.com/questions/42292617/the-imap-server-has-unexpectedly-disconnected-using-993-port-and-mailkit
After that i started digging in your code and found an option to hand over this value from appsettings (if i clearly understand).

public SecureSocketOptions SocketOptions { get; set; } = SecureSocketOptions.Auto;

But there is a small problem that this parameter is not passed in method ConnectAsync on line 79:
await imapClient.ConnectAsync(ImapHost, ImapPort, SecureSocketOptions.Auto, cancellationToken).ConfigureAwait(false);

Could you please pass this parameter in method, please?
And if you do, please show an example of using this parameters in appsettings.
Best regards.

System.InvalidOperationException on trying get mime message.

Hi @danzuep. I want to get MimeMessage while processing arrived message to get attachments and text from it and get error:

System.InvalidOperationException: 'The ImapClient is currently busy processing a command in another thread. Lock the SyncRoot property to properly synchronize your threads.'

Here's my code to reproduce:

using MailKit;
using MailKitSimplified.Receiver.Services;

using var imapReceiver = ImapReceiver.Create("imapHost:imapPort")
    .SetCredential("login", "applicationPassword");

try
{
    await imapReceiver.ConnectAuthenticatedImapClientAsync();
    await imapReceiver.ConnectMailFolderAsync();

    await imapReceiver.MonitorFolder
       .SetMessageSummaryItems(MessageSummaryItems.Full | MessageSummaryItems.BodyStructure | MessageSummaryItems.UniqueId)
       .SetIgnoreExistingMailOnConnect(false)
       .OnMessageArrival(OnArrivalAsync)
       .IdleAsync();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

async Task OnArrivalAsync(IMessageSummary messageSummary)
{
    try
    {
        var mimeMessage = await imapReceiver.ReadMail.GetMimeMessageAsync(messageSummary.UniqueId);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

Or did I miss something and need to receive messages in a different way?

Use Flags - Follow up #9

Follow up question for #9 if this is the 'best' way possible for MailKitSimplified.

Would like to automatically process mails, and then mark them as Seen or Delete them. It looks like the changing of flags isn't implemented in MailKitSimplified, but it's also not possible to retrieve the (possible) underlying ImapClient.

This workaround works, but requires and extra connection.

Is this the best if I would like to use MailKitSimplified?

Proof of concept code:

CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromMinutes(1));

Console.WriteLine("Connecting..");

using ImapReceiver imapReceiver = ImapReceiver.Create(ServerHost, ServerPort)
    .SetCredential(Username, Password)
    .SetProtocolLog("Logs/ImapClient.txt")
    .SetFolder("INBOX");

ImapClient imapClient = new();
imapClient.Connect(ServerHost, ServerPort);
imapClient.Authenticate(Username, Password);
imapClient.Inbox.Open(FolderAccess.ReadWrite);

Console.WriteLine("Connected");

IMailFolderMonitor mailFolderMonitor = imapReceiver.MonitorFolder.SetMessageSummaryItems(MessageSummaryItems.All);

await mailFolderMonitor.OnMessageArrival(m =>
{
    Console.WriteLine($"[{m.UniqueId}] - {m.Envelope.From} {m.NormalizedSubject}");
    imapClient.Inbox.AddFlags(m.UniqueId, MessageFlags.Seen, true);
}).IdleAsync(cancellationTokenSource.Token);

Remove obsolete methods

There's been a build-up of methods that are now marked with the [Obsolete] attribute, remove the dead code associated with them and bump the version.

MailFolderReader disposing

Hi, @danzuep

I've checked you implementation of IAsyncDisposable and IDisposable for IMailFolderReader and It seems to that DisposeAsync should call _imapReceiver?.DisposeAsync() instead of disconnecting since IImapReceiver also implements IAsyncDisposable and IDisposable.

But my issue is not about that, It's more about design. The problem is still here. We can't dispose ReadMail this way. These objects are still being created and stored in memory. But If we will dispose ReadMail object after first read operation then we get ObjectDisposedException on the next call, because _imapReceiver will be disposed.

Therefore, you need to somehow create an MailFolderReader in the ReadMail property that will not refer to the main object _imapReceiver so that we can dispose of it after use.

Since that I prefer to create an ImapClient per message for fetching needed mime message by UniqueId from IMessageSummary object and after all dispose it.

private async Task<IImapClient> CreateImapClientAsync()
{
    var imapClient = new ImapClient()
    {
        Timeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds
    };

    await imapClient.ConnectAsync(EmailClientConfiguration.ImapIp, EmailClientConfiguration.ImapPort);
    await imapClient.AuthenticateAsync(EmailClientConfiguration.Login, EmailClientConfiguration.Password);
    await imapClient.Inbox.OpenAsync(FolderAccess.ReadOnly);

    return imapClient;
}

private async Task OnArrivalAsync(IMessageSummary messageSummary)
{
    try
    {
        using var fetcher = await CreateImapClientAsync();
        var fetchedMessage = await fetcher.Inbox.GetMessageAsync(messageSummary.UniqueId, _cancel.Token);
        // do some processing things with fetched message
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "An error occured while handling arrived message with id: {uniqueId} for email client with id: {emailClientId}!",
            messageSummary.UniqueId.Id, EmailClientConfiguration.ClientId);
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.