IMAP component | Blog | Limilabs https://www.limilabs.com/blog Thu, 28 Mar 2024 14:05:54 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.4 IMAP component for .NET https://www.limilabs.com/blog/imap-component-for-net Wed, 29 Nov 2023 11:51:36 +0000 http://www.limilabs.com/blog/?p=219 Mail.dll email component includes an excellent IMAP email component. Unleash the full potential of email management with Mail.dll’s outstanding IMAP email component. It offers .NET developers an unparalleled tool for handling IMAP email protocol. From retrieving messages to managing folders and attachments, the IMAP component in Mail.dll simplifies complex tasks into user-friendly functions. Mail.dll is […]

The post IMAP component for .NET first appeared on Blog | Limilabs.

]]>
Mail.dll email component includes an excellent IMAP email component. Unleash the full potential of email management with Mail.dll’s outstanding IMAP email component.

It offers .NET developers an unparalleled tool for handling IMAP email protocol. From retrieving messages to managing folders and attachments, the IMAP component in Mail.dll simplifies complex tasks into user-friendly functions.

Mail.dll is available for all regular .NET framework versions as well as for most recent .net 6 and .net 7.

IMAP, short for Internet Message Access Protocol, is one of the two most prevalent Internet standard protocols for e-mail retrieval, the other being Post Office Protocol – POP3 (also available in Mail.dll).

IMAP has many advantages over POP3, most important are using folders to group emails, saving seen/unseen state of the messages and search capability.

This article is going to show how easy is to connect to use IMAP client from .NET, download and parse unseen messages.

Installation

The easiest way to install Mail.dll is to download it from nuget via Package Manager:

PM> Install-Package Mail.dll

Alternatively you can download Mail.dll directly from our website.

Connect to IMAP

First you need connect to IMAP server. You should use Imap class for that. Using block ensures that IMAP connection is properly closed and disposed when you’re done.

using(Imap imap = new Imap())

In most cases you should be using TLS secured connection – ConnectSSL method will choose a correct port and perform SSL/TLS negotiation, then you perform IMAP authentictation:

	imap.ConnectSSL("imap.server.com");
	imap.UseBestLogin("user", "password");

Mail.dll supports OAuth 2.0 for authentication, you can find OAuth2 samples for Office365 and Gmail on the Mail.dll samples page.

For Gmail you may want to use Gmail’s App Passwords.

Search for unseen emails

As IMAP groups messages into folder you need to select a well know INBOX folder. All new, received emails are first placed in the INBOX folder first:

imap.SelectInbox();

Then you perform a search that find all unseen messages

List<long> uids = imap.Search(Flag.Unseen);

Download and parse emails

Finally you download emails one-by-one and parse them. You should use MailBuider class to parse emails:

var eml = imap.GetMessageByUID(uid);
IMail email = new MailBuilder().CreateFromEml(eml);

Entire code to download emails in .net

Entire code to download emails using IMAP is less than 20 lines:

using(Imap imap = new Imap())
{
	imap.ConnectSSL("imap.server.com");	// or Connect for no TLS
	imap.UseBestLogin("user", "password");

	imap.SelectInbox();
	List<long> uids = imap.Search(Flag.Unseen);

	foreach (long uid in uids)
	{
		var eml = imap.GetMessageByUID(uid);
		IMail email = new MailBuilder().CreateFromEml(eml);

		string subject = mail.Subject;
	}
	imap.Close();
}

and for those who like VB.NET more:

Using imap As New Imap()
	imap.ConnectSSL("imap.server.com")
	imap.UseBestLogin("user", "password")

	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search(Flag.Unseen)

	For Each uid As Long In uids
		Dim mail As IMail = New MailBuilder()_
			.CreateFromEml(imap.GetMessageByUID(uid))

		Console.WriteLine(mail.Subject)
	Next
	imap.Close()
End Using

It can’t get much simpler than that!

Summary

You can use Mail.dll .NET IMAP component to:

It works perfectly with Exchange, Office365, Gmail and all other IMAP servers.

Just give Mail.dll a try and download it at: Mail.dll .NET IMAP component


Get Mail.dll

The post IMAP component for .NET first appeared on Blog | Limilabs.

]]>
How to search IMAP in .NET https://www.limilabs.com/blog/how-to-search-imap-in-net https://www.limilabs.com/blog/how-to-search-imap-in-net#comments Mon, 31 Jul 2023 13:49:47 +0000 http://www.limilabs.com/blog/?p=181 One of many advantages IMAP protocol has over POP3 protocol is the ability to search. There are several ways of performing search using Mail.dll .NET IMAP library: IMAP search is done entirely on the server side, which means that it’s fast and doesn’t require Mail.dll client to download much data over the network. Installation The […]

The post How to search IMAP in .NET first appeared on Blog | Limilabs.

]]>
One of many advantages IMAP protocol has over POP3 protocol is the ability to search.

There are several ways of performing search using Mail.dll .NET IMAP library:

  • Search(Flag) – for common flag searches (seen, unseen, …).
  • SimpleImapQuery – easy one object query, where all conditions are joined with an AND operator.
  • Expression syntax – most advanced, allows using AND and OR operators and sorting.

IMAP search is done entirely on the server side, which means that it’s fast and doesn’t require Mail.dll client to download much data over the network.

Installation

The easiest way to install Mail.dll IMAP client for .NET is to download it from nuget via Package Manager:

PM> Install-Package Mail.dll

Alternatively you can download Mail.dll directly from our website.

Connect to IMAP

Let’s start with the basics: we’ll connect to the IMAP server in .NET app and authenticate:

// C# code:

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.example.com");
    imap.UseBestLogin("user", "password");
    imap.SelectInbox();

    // Search code goes here

    imap.Close();
}
' VB.NET code:

Using imap As New Imap()
    imap.ConnectSSL("imap.example.com")
    imap.UseBestLogin("user", "password")
    imap.SelectInbox()

    ' Search code here

    imap.Close()
End Using

When using Gmail you can use application passwords, with Office 365 you’ll need to use OAuth 2.0 to authenticate.

Mail.dll fully supports OAuth 2.0 for authentication, you can find OAuth2 samples for Office365 and Gmail on the Mail.dll samples page.

Search(Flag)

Now, let’s look at the search code. The first and the simplest way to search is by using Imap.Search(Flag) method. This sample below finds all unseen email messages (those that have \UNSEEN flag).

// C#

List<long> uidList = imap.Search(Flag.Unseen);

// now download each message and get the subject
foreach (long uid in uidList)
{
    IMail message = new MailBuilder()
        .CreateFromEml(imap.GetMessageByUID(uid));

    string subject = message.Subject;
}
' VB.NET

Dim uidList As List(Of Long) = imap.Search(Flag.Unseen)

' now download each message and get the subject
For Each uid As Long In uidList
	Dim message As IMail = New MailBuilder()_
		.CreateFromEml(imap.GetMessageByUID(uid))

	Dim subject As String = message.Subject
Next

SimpleImapQuery

Second approach is to use an IMAP query object. The sample below searches for all unseen emails with a certain subject. All non-null SimpleImapQuery properties are combined using and operator.

// C#

SimpleImapQuery query = new SimpleImapQuery();
query.Subject = "subject to search";
query.Unseen = true;

List<long> uids = imap.Search(query);
' VB.NET

Dim query As New SimpleImapQuery()
query.Subject = "subject to search"
query.Unseen = True

Dim uids As List(Of Long) = imap.Search(query)

Expression syntax

Finally, the most advanced search option using Expression class. You can use AND, OR and NOT operators in your IMAP search.

Note that Mail.dll fully encapsulates IMAP search syntax, with easy to use and very readable .NET classes:

// C#

List<long> uids = imap.Search().Where(
    Expression.And(
        Expression.Not(Expression.Subject("subject not to search")),
        Expression.HasFlag(Flag.Unseen)));
' VB.NET

Dim uids As List(Of Long) = imap.Search().Where(_
    Expression.[And](_
        Expression.[Not](Expression.Subject("subject not to search")),_
        Expression.HasFlag(Flag.Unseen)))

Expression syntax with sorting

Expression search syntax also allows sorting (defined in RFC 5256).

This feature is not available for every IMAP server: you need to check if your IMAP server supports ImapExtension.Sort extension first.

// C#

List<long> uids = imap.Search()
    .Where(Expression.And(
            Expression.Not(Expression.Subject("subject not to search")),
            Expression.HasFlag(Flag.Unseen)))
    .Sort(SortBy.Multiple(
            SortBy.Date(),
            SortBy.Subject()));
' VB.NET

Dim uids As List(Of Long) = imap.Search() _
    .Where(Expression.[And]( _
        Expression.[Not](Expression.Subject("subject not to search")), _
        Expression.HasFlag(Flag.Unseen))) _
    .Sort(SortBy.Multiple( _
        SortBy.[Date](), _
        SortBy.Subject()))
)

Notice the neat trick in Mail.dll .NET client, that allows casting FluentSearch class, received from imap.Search() method, to List<longs>:

public static implicit operator List<long>(FluentSearch search)
{
        return search.GetList();
}

We tend to use it very often for builder objects used in our unit tests.

Suggested reading

If you like it just give it a try and download it at: Mail.dll .NET IMAP component


Get Mail.dll

The post How to search IMAP in .NET first appeared on Blog | Limilabs.

]]>
https://www.limilabs.com/blog/how-to-search-imap-in-net/feed 36
Save all attachments to disk using IMAP https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap#comments Fri, 28 Jul 2023 15:41:29 +0000 http://www.limilabs.com/blog/?p=1232 Unlock the potential of efficient email processing in .NET with this comprehensive guide on saving email attachments using Mail.dll’s IMAP component and IMAP protocol. In this article, we’ll walk you through the step-by-step process of downloading email messages using Mail.dll’s IMAP component and, more importantly, demonstrate how to effortlessly save all attachments to your disk. […]

The post Save all attachments to disk using IMAP first appeared on Blog | Limilabs.

]]>
Unlock the potential of efficient email processing in .NET with this comprehensive guide on saving email attachments using Mail.dll’s IMAP component and IMAP protocol.

In this article, we’ll walk you through the step-by-step process of downloading email messages using Mail.dll’s IMAP component and, more importantly, demonstrate how to effortlessly save all attachments to your disk.

Email MIME structure

The first thing you need to know is that email attachments are tightly integrated with the email message itself, ensuring that they are conveniently stored and transport together.

In MIME (Multipurpose Internet Mail Extensions), email attachments are stored together with an email message to ensure a structured and standardized way of transmitting and receiving email content with multiple parts.

The primary reason for storing attachments within the email message is to maintain a single cohesive entity that encapsulates all the components of the email, including its text, formatting, and any attached files. By bundling attachments with the message, the entire email becomes a self-contained package, making it easier to handle and process by email clients and servers.

Attachments in Mail.dll

Invoking Imap.GetMessageByUID method is going to download entire email message, including all attachments.

Attachments are stored within the email as part of a mime tree. Usually Quoted-Printable or Base64 encoding is used.

However, with Mail.dll, you don’t need to worry about navigating this mime tree yourself. The library efficiently parses the email’s structure, effortlessly exposing all attachments as familiar .NET collections. This user-friendly approach simplifies the process of handling email attachments, allowing you to focus on building robust and efficient email management solutions with ease.

There are 4 collections that contain attachments:

  • IMail.Attachments – all attachments (includes Visuals, NonVisuals and Alternatives).
  • IMail.Visuals – visual elements, files that should be displayed to the user, such as images embedded in an HTML email.
  • IMail.NonVisuals – non visual elements, “real” attachments.
  • IMail.Alternatives – alternative content representations, for example ical appointment.

How to download all email attachments in .NET

To retrieve all attachments from an email, you can take advantage of the IMail.Attachments collection provided by Mail.dll. This collection serves as a comprehensive repository, housing all the attachments associated with the email.

Each attachment is represented by a dedicated MimeData object, which encapsulates the specific data and metadata of the attachment.

Using the IMail.Attachments collection eliminates the need for manual traversal of the email’s mime tree or deciphering complex structures. Instead, Mail.dll handles the heavy lifting for you, ensuring a streamlined and user-friendly experience when accessing email attachments programmatically.

Installation

The easiest way to install Mail.dll is to download it from nuget via Package Manager:

PM> Install-Package Mail.dll

Alternatively you can download Mail.dll directly from our website.

Download emails and all attachments

The C# code to download all emails and save all attachments using Mail.dll and IMAP is as follows.

using Limilabs.Client.IMAP;
using Limilabs.Mail;
using Limilabs.Mail.MIME
using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        using(Imap imap = new Imap())
        {
            imap.ConnectSSL("imap.example.com");
            imap.UseBestLogin("user", "password");

            imap.SelectInbox();
            List<long>; uids = imap.Search(Flag.Unseen);
            foreach (long uid in uids)
            {
                var eml = imap.GetMessageByUID(uid);
                IMail email = new MailBuilder()
                    .CreateFromEml(eml);
                Console.WriteLine(email.Subject);

                foreach (MimeData mime in email.Attachments)
                {
                    mime.Save(@"c:\" + mime.SafeFileName);
                }
            }
            imap.Close();
        }
    }
};

Below is the VB.NET code for your reference:

Imports Limilabs.Client.IMAP
Imports Limilabs.Mail
Imports Limilabs.Mail.MIME
Imports System
Imports System.Collections.Generic

Public Module Module1
    Public Sub Main(ByVal args As String())

        Using imap As New Imap()
            imap.ConnectSSL("imap.example.com")
            imap.UseBestLogin("user", "password")

            imap.SelectInbox()
            Dim uids As List(Of Long) = imap.Search(Flag.Unseen)

            For Each uid As Long In uids
                Dim eml = imap.GetMessageByUID(uid)
                Dim email As IMail = New MailBuilder() _
                    .CreateFromEml(eml)
                Console.WriteLine(email.Subject)

                For Each mime As MimeData In email.Attachments
                    mime.Save("c:\" + mime.SafeFileName)
                Next
            Next
            imap.Close()
        End Using

    End Sub
End Module

Accessing attachment data

You can also save attachment to a specific stream using MimeData.Save(Stream stream).

You can get direct access to attachment binary data with MimeData.GetMemoryStream().

Finally you can get a byte array (byte[]) using MimeData.Data property.

Downloading only parts of the message

IMAP protocol provides very useful features in regard to working with attachments and all are available in Mail.dll .NET IMAP component.

With Mail.dll you can download only parts of email message, which in conjunction with getting basic email information without downloading entire message can make your code extremely fast.

Process emails embedded as attachments

In some situations you’ll receive a message that has another message attached.

You can use Mail.dll to extract all attachments from such inner messages no matter how deep the embedding level is.


Get Mail.dll

The post Save all attachments to disk using IMAP first appeared on Blog | Limilabs.

]]>
https://www.limilabs.com/blog/save-all-attachments-to-disk-using-imap/feed 10
Office 365 enable IMAP/POP3 and SMTP access https://www.limilabs.com/blog/office365-enable-imap-pop3-smtp Fri, 18 Feb 2022 12:56:34 +0000 https://www.limilabs.com/blog/?p=5901 First log in to Microsoft 365 admin portal at https://admin.microsoft.com/ as an administrator, go to Org settings screen and find Modern authentication entry: Check ‘Turn on modern authentication…‘ for OAuth flows. Check IMAP, POP3 and SMTP for App passwords flows. Then go to Users screen: Select an user and on the Mail tab click Manage […]

The post Office 365 enable IMAP/POP3 and SMTP access first appeared on Blog | Limilabs.

]]>
First log in to Microsoft 365 admin portal at https://admin.microsoft.com/ as an administrator, go to Org settings screen and find Modern authentication entry:

Check ‘Turn on modern authentication…‘ for OAuth flows.

Check IMAP, POP3 and SMTP for App passwords flows.

Then go to Users screen:

Select an user and on the Mail tab click Manage email apps

Check IMAP, Pop and Authenticated SMTP to turn on the protocols for this account

Have in mind it takes 20-30 minutes for the changes to take effect.

AD configuration

In your Active Directory, make sure Enable Security defaults is set to No:

Make sure there are no Conditional Access | Policies defined in your AD:

Authentication – Basic Auth [deprecated]

It is no longer possible to re-enable Basic Auth or use App passwords.

To use basic authentication (username/password) you’ll need to
Re-enable Basic Auth for your tenant

For MFA enabled/enforced accounts you must
Create and use App passwords

using (Imap imap = new Imap())
{
    imap.ConnectSSL("outlook.office365.com");
 
    imap.UseBestLogin(
        "AdeleV@limilabs.onmicrosoft.com",  
        "password");
 
    imap.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
 
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
            .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
    }
 
    imap.Close();
}

Authentication – OAuth 2.0

Daemons/Services: Password grant (MFA/2FA must be turned off for this account):
https://www.limilabs.com/blog/oauth2-password-grant-office365-exchange-imap-pop3-smtp

Daemons/Services: Client credential flow:
https://www.limilabs.com/blog/oauth2-client-credential-flow-office365-exchange-imap-pop3-smtp

Web apps (requires user interaction):
https://www.limilabs.com/blog/oauth2-web-flow-office365-exchange-imap-pop3-smtp

Standalone devices (requires very little interaction):
https://www.limilabs.com/blog/oauth2-device-flow-office365-exchange-imap-pop3-smtp

Desktop apps (requires user interaction):
https://www.limilabs.com/blog/oauth2-office365-exchange-imap-pop3-smtp

using (Imap imap = new Imap())
{
    imap.ConnectSSL("outlook.office365.com");
 
    imap.UseBestLogin(
        "AdeleV@limilabs.onmicrosoft.com",  
        "access-token");
 
    imap.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);
 
    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
            .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
    }
 
    imap.Close();
}

Exchange administration

You can find the same mailbox/user settings through Exchange administration screens:

The post Office 365 enable IMAP/POP3 and SMTP access first appeared on Blog | Limilabs.

]]>
Office 365 App passwords https://www.limilabs.com/blog/office365-app-passwords Fri, 18 Feb 2022 12:35:08 +0000 https://www.limilabs.com/blog/?p=5890 It is no longer possible to re-enable Basic Auth or use App passwords. You’ll need to use one of the OAuth 2.0 flows:   OAuth 2.0 with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 web flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 password grant with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 device flow with Office365/Exchange IMAP/POP3/SMTP OAuth 2.0 client credential flow […]

The post Office 365 App passwords first appeared on Blog | Limilabs.

]]>
It is no longer possible to re-enable Basic Auth or use App passwords.

First make sure IMAP/POP3/SMTP are turned on properly: enable IMAP / POP3 and SMTP in Office 365

To use App passwords, Multi-factor Authentication (MFA) must be turned on for the account.

On the other had, if you have MFA turned on for your account, App passwords are the simplest way to receive your emails – your primary password won’t work.

First log in to Microsoft 365 admin portal at https://admin.microsoft.com/ as an administrator. Go to Users screen and click Multi-factor authentication:

Select a user and change Multi-factor Auth Status to Enforced:

Log in as this user to his My Account at https://myaccount.microsoft.com/, then go My sign-ins and create a new App password on the Security info screen:

Write down the created password (you won’t be able to see it again).

Have in mind it takes 20-30 minutes for the changes to take effect.

Remember to turn IMAP on for those Office 365 accounts as well, now you can now use it as a standard password:

using (Imap imap = new Imap())
{
    imap.ConnectSSL("outlook.office365.com");

    imap.UseBestLogin(
        "AlexW@limilabs.onmicrosoft.com", 
        "qfhnlzsdaqew");

    imap.SelectInbox();

    List<long> uids = imap.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        IMail email = new MailBuilder()
            .CreateFromEml(imap.GetMessageByUID(uid));
        string subject = email.Subject;
    }

    imap.Close();
}

You can find more details here as well:
https://support.microsoft.com/en-us/account-billing/using-app-passwords-with-apps-that-don-t-support-two-step-verification-5896ed9b-4263-e681-128a-a6f2979a7944

The post Office 365 App passwords first appeared on Blog | Limilabs.

]]>
OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) https://www.limilabs.com/blog/oauth2-gmail-imap-service-account-dotnetopenauth Sat, 12 Mar 2016 22:00:00 +0000 http://www.limilabs.com/blog/?p=4810 Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version. In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password. […]

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Consider using Google.Apis version for service account Gmail access instead of DotNetOpenAuth version.

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

This scenario is very similar to 2-legged OAuth, which uses OAuth 1.0a. Although it still works, it has been deprecated by Google and OAuth 2.0 service accounts were introduced.

The following describes how to use XOAUTH2 and OAuth 2.0 to achieve the equivalent of 2-legged OAuth.

Google APIs console

First you need to visit the Google Developers Console and create a service account:

console_0
console_1
console_2

Download and save this private key, you’ll need that later:

console_3

Go to “Service accounts” and click “View Client ID”:

console_4

Make a note of the Client ID and Email address (Service account).

Google Apps Dashboard

Next step is to authorize access for newly created service account.

Visit your domain administration panel:
https://www.google.com/a/cpanel/yourdomain.com/ManageOauthClients

Then click “Advanced tools”, “Authentication” and “Manage third party OAuth Client access”.

On this screen you can authorize service account to access email scope:

cpanel_1

Use previously remembered Client ID and “https://mail.google.com/”, which is IMAP/SMTP API scope:

Client Name: 1234567890
One or More API Scopes: https://mail.google.com/

DotNetOpenAuth

DotNetOpenAuth is free, open source library that implements OAuth 2.0.

However you can not use the latest version. This is because Google has not updated their code to work with the most recent release. You’ll need to use 4.0 version.

Newtonsoft.Json

The other library you’ll need is Newtonsoft.Json JSON library. You can download it here: https://github.com/JamesNK/Newtonsoft.Json/releases or use nuget.

Access IMAP/SMTP server

using System;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Newtonsoft.Json;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.OAuth2;
using DotNetOpenAuth.OAuth2.Messages;
using Limilabs.Client.Authentication.Google;
using Limilabs.Client.IMAP;
using Limilabs.Mail;

const string serviceAccountEmail = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.com";

X509Certificate2 certificate =  new X509Certificate2(
    serviceAccountCertPath,
    serviceAccountCertPassword,
    X509KeyStorageFlags.Exportable);

AuthorizationServerDescription server = new AuthorizationServerDescription
    {
        AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
        TokenEndpoint = new Uri("https://oauth2.googleapis.com/token"),
        ProtocolVersion = ProtocolVersion.V20,
    };

AssertionFlowClient provider = new AssertionFlowClient(server, certificate)
{
    ServiceAccountId = serviceAccountEmail,
    Scope = Limilabs.Client.Authentication.Google.GoogleScope.ImapAndSmtp.Name,
    ServiceAccountUser = userEmail,
};

IAuthorizationState grantedAccess = AssertionFlowClient.GetState(provider);
string accessToken = grantedAccess.AccessToken;

using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.LoginOAUTH2(userEmail, accessToken);

    client.SelectInbox();
    List<long> uids = client.Search(Flag.Unseen);

    foreach (long uid in uids)
    {
        var eml = client.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }
    client.Close();
}




AssertionFlowClient

I’ll need to add several classes defined below. You can also browse Google APIs Client Library for .NET.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Limilabs.Client;
using Limilabs.Client.Authentication.SSPI.Ntlm;

/// <summary>
/// Assertion flow header used to generate the assertion flow message header.
/// </summary>
public class AssertionFlowHeader
{
    /// <summary>
    /// Gets or sets the encryption algorithm used by the assertion flow message.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("alg")]
    public String Algorithm { get; set; }

    /// <summary>
    /// Gets or sets the type of the claim.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("typ")]
    public String Type { get; set; }
};

/// <summary>
/// Google assertion flow header holding Google supported values.
/// </summary>
public class GoogleAssertionFlowHeader : AssertionFlowHeader
{

    /// <summary>
    /// The google signing algorithm, currently RSA-SHA256
    /// </summary>
    public const string GoogleSigningAlgorithm = "RS256";

    /// <summary>
    /// The type of the google assertion, currently JSON Web Token
    /// </summary>
    public const string GoogleAssertionType = "JWT";

    public GoogleAssertionFlowHeader()
    {
        Algorithm = GoogleSigningAlgorithm;
        Type = GoogleAssertionType;
    }
};

/// <summary>
/// Assertion flow claim used to generate the assertion flow message claim.
/// </summary>
public class AssertionFlowClaim
{
    public AssertionFlowClaim()
    {
        DateTime begin = new DateTime(1970, 01, 01, 0, 0, 0, DateTimeKind.Utc);
        IssuedAt = (long)(DateTime.UtcNow - begin).TotalSeconds;
        ExpiresAt = IssuedAt + 3600;
    }

    public AssertionFlowClaim(AuthorizationServerDescription authorizationServer)
        : this()
    {
        Audience = authorizationServer.TokenEndpoint.ToString();
    }

    /// <summary>
    /// Gets or sets the assertion flow issuer (e.g client ID).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iss")]
    public String Issuer { get; set; }

    /// <summary>
    /// Gets or sets the service account user (for domain-wide delegation).
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("prn")]
    public String Principal { get; set; }

    /// <summary>
    /// Gets or sets the scope.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("scope")]
    public String Scope { get; set; }

    /// <summary>
    /// Gets or sets the token endpoint.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("aud")]
    public String Audience { get; set; }

    /// <summary>
    /// Gets or sets the expected expiration of the token to retrieve.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("exp")]
    public long ExpiresAt { get; set; }

    /// <summary>
    /// Gets or sets the UTC timestamp at which this claim has been built.
    /// </summary>
    [Newtonsoft.Json.JsonPropertyAttribute("iat")]
    public long IssuedAt { get; set; }
};

/// <summary>
/// Assertion flow message to be sent to the token endpoint.
/// </summary>
public class AssertionFlowMessage : MessageBase
{
    /// <summary>
    /// Google supported assertion type
    /// </summary>
    public const string GoogleAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer";

    /// <summary>
    /// Initializes a new instance of the <see cref="AssertionFlowMessage"/> class.
    /// </summary>
    /// <param name='authorizationServer'> Authorization server description. </param>
    public AssertionFlowMessage(AuthorizationServerDescription authorizationServer) :
        base(new Version(2, 0), MessageTransport.Direct, authorizationServer.TokenEndpoint)
    {
        GrantType = "assertion";
        AssertionType = GoogleAssertionType;
        this.HttpMethods = HttpDeliveryMethods.PostRequest;
    }

    /// <summary>
    /// Gets or sets the type of the grant (defaults to "assertion").
    /// </summary>
    [MessagePart("grant_type", IsRequired = true)]
    public String GrantType { get; set; }

    /// <summary>
    /// Gets or sets the type of the assertion
    /// (defaults to "http://oauth.net/grant_type/jwt/1.0/bearer").
    /// </summary>
    [MessagePart("assertion_type", IsRequired = true)]
    public String AssertionType { get; set; }

    /// <summary>
    /// Gets or sets the assertion message.
    /// </summary>
    [MessagePart("assertion", IsRequired = true)]
    public String Assertion { get; set; }
};

public class AssertionFlowClient : ClientBase
{
    /// <summary>
    /// Gets or sets the service account identifier.
    /// </summary>
    /// <value>
    /// The service account identifier.
    /// </value>
    public String ServiceAccountId { get; set; }

    /// <summary>
    /// Gets or sets the service account user (used for domain-wide delegation).
    /// </summary>
    public String ServiceAccountUser { get; set; }

    /// <summary>
    /// Gets or sets the scope to get access for.
    /// </summary>
    public String Scope { get; set; }

    /// <summary>
    /// Gets the certificate used to sign the assertion.
    /// </summary>
    public X509Certificate2 Certificate { get; private set; }

    /// <summary>
    /// Gets or sets the JWT claim's header (defaults to Google's supported values).
    /// </summary>
    public AssertionFlowHeader Header { get; set; }

    public RSACryptoServiceProvider Key { get; private set; }

    /// <summary>
    /// Initializes a new instance of the
    /// <see cref="AssertionFlowClient"/> class.
    /// </summary>
    /// <param name='authorizationServer'>
    /// Authorization server description.
    /// </param>
    /// <param name='certificate'>
    /// Certificate to use to sign the assertion flow messages.
    /// </param>
    public AssertionFlowClient(
        AuthorizationServerDescription authorizationServer,
        X509Certificate2 certificate)
        : base(authorizationServer, null, null)
    {
        if (certificate == null)
            throw new ArgumentNullException("certificate");
        if (certificate.PrivateKey == null)
            throw new ArgumentNullException("certificate.PrivateKey");

        Header = new GoogleAssertionFlowHeader();
        Certificate = certificate;

        // Workaround to correctly cast the private key as a RSACryptoServiceProvider type 24
        RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
        byte[] privateKeyBlob = rsa.ExportCspBlob(true);

        Key = new RSACryptoServiceProvider();
        Key.ImportCspBlob(privateKeyBlob);
    }

    /// <summary>
    /// Helper method to retrieve the Authorization State.
    /// </summary>
    /// <returns>
    /// The authorization state.
    /// </returns>
    /// <param name='provider'>
    /// The provider to use to retrieve the authorization state.
    /// </param>
    public static IAuthorizationState GetState(AssertionFlowClient provider)
    {
        if (provider.Scope == null)
            throw new ArgumentNullException("Scope");
        IAuthorizationState state = new AuthorizationState(provider.Scope.Split(' '));

        if (provider.RefreshToken(state, null))
        {
            return state;
        }
        return null;
    }

    /// <summary>
    /// Request a new access token using the OAuth 2.0 assertion flow.
    /// </summary>
    /// <returns>
    /// Whether or not a new access token has been successfully retrieved.
    /// </returns>
    /// <param name='authorization'>
    /// Object containing the current authorization state.
    /// </param>
    /// <param name='skipIfUsefulLifeExceeds'>
    /// If set to <c>true</c> skip if useful life exceeds.
    /// </param>
    public new bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds)
    {
        return RefreshToken(authorization, skipIfUsefulLifeExceeds, this.Channel.Request);
    }

    public bool RefreshToken(
        IAuthorizationState authorization,
        TimeSpan? skipIfUsefulLifeExceeds,
        Func<IDirectedProtocolMessage, IProtocolMessage> requestProvider)
    {
        if (authorization == null)
            throw new ArgumentNullException("authorization");
        if (this.Certificate == null)
            throw new ArgumentNullException("Certificate");

        // Check if the token is still valid.
        if (skipIfUsefulLifeExceeds.HasValue && authorization.AccessTokenExpirationUtc.HasValue)
        {
            TimeSpan timeSpan = authorization.AccessTokenExpirationUtc.Value - DateTime.UtcNow;
            if (timeSpan > skipIfUsefulLifeExceeds.Value)
            {
                return false;
            }
        }

        AssertionFlowMessage requestMessage = GenerateMessage();

        var response = requestProvider(requestMessage);

        // Response is not strongly-typed to an AccessTokenSuccessResponse because DotNetOpenAuth can't infer the
        // type from the request message type. The only way to get access to the result data is through the
        // resulting Dictionary.
        if (response.ExtraData.ContainsKey("access_token") && response.ExtraData.ContainsKey("expires_in"))
        {
            authorization.AccessToken = response.ExtraData["access_token"];
            long expiresIn = long.Parse(response.ExtraData["expires_in"]);
            DateTime utcNow = DateTime.UtcNow;
            authorization.AccessTokenExpirationUtc = utcNow.AddSeconds(expiresIn);
            authorization.AccessTokenIssueDateUtc = utcNow;
            authorization.SaveChanges();
            return true;
        }
        return false;
    }

    /// <summary>
    /// Generates the assertion flow message to be sent to the token endpoint.
    /// </summary>
    /// <returns>
    /// The assertion flow message.
    /// </returns>
    private AssertionFlowMessage GenerateMessage()
    {
        string header = JsonConvert.SerializeObject(Header);
        string claim = JsonConvert.SerializeObject(
            new AssertionFlowClaim(AuthorizationServer)
            {
                Issuer = this.ServiceAccountId,
                Principal = this.ServiceAccountUser,
                Scope = this.Scope
            });

        StringBuilder assertion = new StringBuilder();
        assertion.Append(UnpaddedUrlSafeBase64Encode(header));
        assertion.Append(".");
        assertion.Append(UnpaddedUrlSafeBase64Encode(claim));

        // TODO: Check if this is working on FIPS enabled systems.
        byte[] data = Encoding.ASCII.GetBytes(assertion.ToString());
        String signature = UnpaddedUrlSafeBase64Encode(Key.SignData(data, "SHA256"));
        assertion.Append(".");
        assertion.Append(signature);

        return new AssertionFlowMessage(this.AuthorizationServer)
        {
            Assertion = assertion.ToString()
        };
    }

    /// <summary>
    /// Encode the provided UTF8 string into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='value'>
    /// String to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(String value)
    {
        return UnpaddedUrlSafeBase64Encode(Encoding.UTF8.GetBytes(value));
    }

    /// <summary>
    /// Encode the byte array into an URL safe base64 string.
    /// </summary>
    /// <returns>
    /// The URL safe base64 string.
    /// </returns>
    /// <param name='bytes'>
    /// Bytes to encode.
    /// </param>
    private String UnpaddedUrlSafeBase64Encode(Byte[] bytes)
    {
        return Convert.ToBase64String(bytes)
            .Replace("=", String.Empty)
            .Replace('+', '-')
            .Replace('/', '_');
    }
};

The post OAuth 2.0 with Gmail over IMAP for service account (DotNetOpenAuth) first appeared on Blog | Limilabs.

]]>
Delete email permanently in Gmail https://www.limilabs.com/blog/delete-email-permanently-in-gmail https://www.limilabs.com/blog/delete-email-permanently-in-gmail#comments Fri, 04 Sep 2015 10:00:37 +0000 http://www.limilabs.com/blog/?p=575 If you delete a message from your Inbox or one of your custom folders, it will still appear in [Gmail]/All Mail. Here’s why: in most folders, deleting a message simply removes that folder’s label from the message, including the label identifying the message as being in your Inbox. [Gmail]/All Mail shows all of your messages, […]

The post Delete email permanently in Gmail first appeared on Blog | Limilabs.

]]>

If you delete a message from your Inbox or one of your custom folders, it will still appear in [Gmail]/All Mail.

Here’s why: in most folders, deleting a message simply removes that folder’s label from the message, including the label identifying the message as being in your Inbox.

[Gmail]/All Mail shows all of your messages, whether or not they have labels attached to them.

If you want to permanently delete a message from all folders:

  1. Move it to the [Gmail]/Trash folder (or [Gmail]/Bin or national version).
  2. Delete it from the [Gmail]/Trash folder (or [Gmail]/Bin or national version).

All emails in [Gmail]/Spam and [Gmail]/Trash are deleted after 30 days.
If you delete a message from [Gmail]/Spam or [Gmail]/Trash, it will be deleted permanently.

Here’s how this looks like using Mail.dll .NET IMAP component:

Delete emails permanently (C# version)

// C# version:

using(Imap imap = new Imap())
{
	imap.ConnectSSL("imap.gmail.com");
	imap.UseBestLogin("user@gmail.com", "password");

	// Recognize Trash folder
	List<FolderInfo> folders = imap.GetFolders();
 	CommonFolders common = new CommonFolders(folders);
 	FolderInfo trash = common.Trash;

	// Find all emails we want to delete
	imap.SelectInbox();
	List<long> uids = imap.Search(
		Expression.Subject("email to delete"));

	// Move email to Trash
	List<long> uidsInTrash = new List<long>();
	foreach (long uid in uids)
	{
		long uidInTrash = (long)imap.MoveByUID(uid, trash);
		uidsInTrash.Add(uidInTrash);
	}

	// Delete moved emails from Trash
	imap.Select(trash);
	foreach (long uid in uidsInTrash)
	{
		imap.DeleteMessageByUID(uid);
	}

	imap.Close();
}

Delete emails permanently (VB.NET version)

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.UseBestLogin("user@gmail.com", "password")

	' Recognize Trash folder
	Dim folders As List(Of FolderInfo) = imap.GetFolders()
	Dim common As CommonFolders = new CommonFolders(folders)
	Dim trash As FolderInfo = common.Trash

	' Find all emails we want to delete
	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search( _
		Expression.Subject("email to delete"))

	' Move email to Trash
	Dim uidsInTrash As New List(Of Long)
	For Each uid As Long In uids
		Dim uidInTrash As Long = imap.MoveByUID(uid,trash)
		uidsInTrash.Add(uidInTrash)
	Next

	' Delete moved emails from Trash
	imap.[Select](trash)
	For Each uid As Long In uidsInTrash
		imap.DeleteMessageByUID(uid)
	Next

	imap.Close()
End Using

You can also use bulk methods to handle large amount of emails faster:

Delete emails permanently in bulk (C# version)

using(Imap imap = new Imap())
{
	imap.ConnectSSL("imap.gmail.com");
	imap.UseBestLogin("user@gmail.com", "password");

	// Recognize Trash folder
	List<FolderInfo> folders = imap.GetFolders();
 	CommonFolders common = new CommonFolders(folders);
 	FolderInfo trash = common.Trash;

	// Find all emails we want to delete
	imap.SelectInbox();
	List<long> uids = imap.Search(
		Expression.Subject("email to delete"));

	// Move emails to Trash
	List<long> uidsInTrash = imap.MoveByUID(uids, trash);

	// Delete moved emails from Trash
	imap.Select(trash);
	imap.DeleteMessageByUID(uidsInTrash);

	imap.Close();
}

Delete emails permanently in bulk (VB.NET version)

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.UseBestLogin("user@gmail.com", "password")

	' Recognize Trash folder
	Dim folders As List(Of FolderInfo) = imap.GetFolders()
	Dim common As CommonFolders = new CommonFolders(folders)
	Dim trash As FolderInfo = common.Trash

	' Find all emails we want to delete
	imap.SelectInbox()
	Dim uids As List(Of Long) = imap.Search( _
		Expression.Subject("email to delete"))

	' Move email to Trash
	Dim uidsInTrash As List(Of Long) = imap.MoveByUID(uids, trash)

	' Delete moved emails from Trash
	imap.[Select](trash)
	imap.DeleteMessageByUID(uidsInTrash)

	imap.Close()
End Using

Please also note that there are 2 additional sections in the “Gmail settings”/”Forwarding and POP/IMAP” tab:

“When I mark a message in IMAP as deleted:”

  • Auto-Expunge on – Immediately update the server. (default)
  • Expunge off – Wait for the client to update the server.

“When a message is marked as deleted and expunged from the last visible IMAP folder:”

  • Archive the message (default)
  • Move the message to the Trash
  • Immediately delete the message forever

Those settings change the default behavior of the account and modify DeleteMessage* methods behavior.

The post Delete email permanently in Gmail first appeared on Blog | Limilabs.

]]>
https://www.limilabs.com/blog/delete-email-permanently-in-gmail/feed 18
OAuth 2.0 with Gmail over IMAP for service account https://www.limilabs.com/blog/oauth2-gmail-imap-service-account Thu, 11 Sep 2014 09:36:51 +0000 http://www.limilabs.com/blog/?p=3904 You can also read how to use:   OAuth 2.0 with Gmail over IMAP for web applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for installed applications (Google.Apis) OAuth 2.0 with Gmail over IMAP for service account (Google.Apis) In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, […]

The post OAuth 2.0 with Gmail over IMAP for service account first appeared on Blog | Limilabs.

]]>
You can also read how to use:

 

In this article I’ll show how to access Gmail account of any domain user, using OAuth 2.0, .NET IMAP component and service accounts. The basic idea is that domain administrator can use this method to access user email without knowing user’s password.

This scenario is very similar to 2-legged OAuth, which uses OAuth 1.0a. Although it still works, it has been deprecated by Google and OAuth 2.0 service accounts were introduced.

The following describes how to use XOAUTH2 and OAuth 2.0 to achieve the equivalent of 2-legged OAuth.

Google.Apis

Use Nuget to download “Google.Apis.Auth” package.

Import namespaces:

// c#

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Requests;
using Google.Apis.Auth.OAuth2.Responses;

using System.Security.Cryptography.X509Certificates;

using Limilabs.Client.Authentication.Google;

using Limilabs.Client.IMAP;
' VB.NET 

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Requests
Imports Google.Apis.Auth.OAuth2.Responses

Imports System.Security.Cryptography.X509Certificates

Imports Limilabs.Client.Authentication.Google

Imports Limilabs.Client.IMAP

Google Cloud

First you need to visit Google Cloud Console and create a project:

Now create a new service account:

Add a service name and remember an email address assigned to your service:

Then you need to create a private key for this service:

Download and save this private key (XYZ.p12), you’ll need that later:

Google Domain administration

Final part is to allow this service to access your domain. You’ll perform this steps in your domain administration panel.

Remember the Client ID first, and go to your domain administration panel:

In the main menu select Security / Access and data control / API controls

Then Manage domain wide delegation

Use previously remembered Client ID and https://mail.google.com/, which is IMAP/SMTP API scope:

Alternatively you can use https://www.googleapis.com/auth/gmail.imap_admin scope.

When authorized with this scope, IMAP connections behave differently:

  • All labels are shown via IMAP, even if users disabled “Show in IMAP” for the label in the Gmail settings.
  • All messages are shown via IMAP, regardless of what the user set in “Folder Size Limits” in the Gmail settings.

Access IMAP/SMTP server

// C#

const string serviceAccountEmail = "name@xxxxxxxxxx.gserviceaccount.com";
const string serviceAccountCertPath = @"c:\XYZ.p12";
const string serviceAccountCertPassword = "notasecret";
const string userEmail = "user@your-domain.com";

X509Certificate2 certificate = new X509Certificate2(
    serviceAccountCertPath,
    serviceAccountCertPassword,
    X509KeyStorageFlags.Exportable);

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
        Scopes = new[] { "https://mail.google.com/" },
        // Scopes = new[] { "https://www.googleapis.com/auth/gmail.imap_admin" },

        User = userEmail
    }.FromCertificate(certificate));

bool success = await credential.RequestAccessTokenAsync(
    CancellationToken.None);

using (Imap imap = new Imap())
{
    imap.ConnectSSL("imap.gmail.com");
    imap.LoginOAUTH2(userEmail, credential.Token.AccessToken);

    imap.SelectInbox();

    foreach (long uid in uids)
    {
        var eml = client.GetMessageByUID(uid);
        IMail email = new MailBuilder().CreateFromEml(eml);
        Console.WriteLine(email.Subject);
    }

    imap.Close();
}
' VB.NET

Const serviceAccountEmail As String = "name@xxxxxxxxxx.gserviceaccount.com"
Const serviceAccountCertPath As String = "c:\XYZ.p12"
Const serviceAccountCertPassword As String = "notasecret"
Const userEmail As String = "user@your-domain.com"

Dim certificate As New X509Certificate2(serviceAccountCertPath, serviceAccountCertPassword, X509KeyStorageFlags.Exportable)

Dim credential As New ServiceAccountCredential(New ServiceAccountCredential.Initializer(serviceAccountEmail) With { _
	.Scopes = {"https://mail.google.com/"}, _
	' .Scopes = {"https://www.googleapis.com/auth/gmail.imap_admin"}, _
	.User = userEmail _
}.FromCertificate(certificate))

Dim success As Boolean = credential.RequestAccessTokenAsync(
    CancellationToken.None).Result

Using imap As New Imap()
	imap.ConnectSSL("imap.gmail.com")
	imap.LoginOAUTH2(userEmail, credential.Token.AccessToken)

	imap.SelectInbox()

	For Each uid As Long In uids
		Dim eml = client.GetMessageByUID(uid)
		Dim email As IMail = New MailBuilder().CreateFromEml(eml)
		Console.WriteLine(email.Subject)
	Next

	imap.Close()
End Using

The post OAuth 2.0 with Gmail over IMAP for service account first appeared on Blog | Limilabs.

]]>
IMAP search requires parentheses https://www.limilabs.com/blog/imap-search-requires-parentheses Fri, 06 Jun 2014 06:58:42 +0000 http://www.limilabs.com/blog/?p=4621 IMAP protocol uses Polish notation (also known as prefix notation) for client queries. In general it means, that operator (in case of IMAP search it’s always a logical operator) is placed left of its operands. Polish notation Expression that would be written in conventional infix notation as: (5 − 6) * 7 can be written […]

The post IMAP search requires parentheses first appeared on Blog | Limilabs.

]]>
IMAP protocol uses Polish notation (also known as prefix notation) for client queries. In general it means, that operator (in case of IMAP search it’s always a logical operator) is placed left of its operands.

Polish notation

Expression that would be written in conventional infix notation as:
(5 − 6) * 7
can be written in prefix/Polish notation as:
* (− 5 6) 7
As all mathematical operands are binary (or have defined number of operands) we don’t need parentheses to correctly evaluate this expression:
* − 5 6 7

Polish notation doesn’t require parentheses.

IMAP search needs parentheses

Although IMAP search syntax uses Polish notation it actually requires parentheses to be evaluated unambiguously.

This is because there is no explicitly defined AND operator, that takes exactly 2 operands.

RFC 3501:

When multiple keys are specified, the result is the intersection (AND function) of all the messages that match those keys.

So although it is true that Polish and reverse Polish notations don’t require parentheses, it is not true that IMAP search doesn’t require them.

‘OR/AND’ example

Expression 1a:

Regular notation:
(SUBJECT subject OR BODY body) AND FROM from

Polish notation:
AND OR SUBJECT subject BODY body FROM from

IMAP’s Polish notation (note there is no AND operator):
OR SUBJECT subject BODY body FROM from

Expression 1b:

Regular notation:
SUBJECT subject OR (BODY body AND FROM from)

Polish notation:
OR SUBJECT subject AND BODY body FROM from

IMAP’s Polish notation (note there is no AND operator):
OR SUBJECT subject BODY body FROM from

1a != 1b

Expression 1a is not equal to Expression 1b:
((a or b) and c) != (a or (b and c))

For:
a = true
b = true
c = false

We have:
((true or true) and false) != (true or (true and false))
false != true

Parentheses are needed

But when there is no AND operator, IMAP’s Polish notations look exactly the same, thus we need parentheses!

Expression 1a (IMAP’s Polish notation with parentheses):
(OR SUBJECT subject BODY body) FROM from

Expression 1b (IMAP’s Polish notation with parentheses):
OR SUBJECT subject (BODY body FROM from)

OR SUBJECT subject BODY body FROM from
is treated like
(OR SUBJECT subject BODY body) FROM from

‘NOT’ example

Expression 2a:

Regular notation:
NOT (SUBJECT subject AND BODY body)

Polish notation:
NOT AND SUBJECT subject BODY body

IMAP’s Polish notation (note there is no AND operator):
NOT SUBJECT subject BODY body

Expression 2b:

Regular notation:
(NOT SUBJECT subject) AND BODY body

Polish notation:
AND NOT SUBJECT subject BODY body

IMAP’s Polish notation (note there is no AND operator):
NOT SUBJECT subject BODY body

2a != 2b

Expression 2a is obviously not equal to Expression 2b.

Parentheses are needed

Again IMAP’s Polish notations look the same. We need parentheses!

Expression 2a (IMAP’s Polish notation with parentheses):
NOT (SUBJECT subject BODY body)

Expression 2b (IMAP’s Polish notation with parentheses):
(NOT SUBJECT subject) BODY body

NOT SUBJECT subject BODY body
is treated like
(NOT SUBJECT subject) BODY body

The post IMAP search requires parentheses first appeared on Blog | Limilabs.

]]>
Search unanswered emails in Gmail https://www.limilabs.com/blog/search-unanswered-emails-in-gmail Fri, 01 Nov 2013 13:44:36 +0000 http://www.limilabs.com/blog/?p=4387 IMAP protocol in RFC3501 introduced \Answered flag. \Answered flag should mark messages that have been answered. This flag can by set by client applications or even by SMTP server, which can be examining In-Reply-To email headers. Unfortunately if the message is answered through Gmail’s web interface, the \Answered flag will not be set. It will […]

The post Search unanswered emails in Gmail first appeared on Blog | Limilabs.

]]>
IMAP protocol in RFC3501 introduced \Answered flag. \Answered flag should mark messages that have been answered. This flag can by set by client applications or even by SMTP server, which can be examining In-Reply-To email headers.

Unfortunately if the message is answered through Gmail’s web interface, the \Answered flag will not be set. It will only be set if the messages were answered using an email program that sets this flag.

This means that \Answered flag can’t be used to find not-answered emails.

One option to resolve this problem is to use Gmail IMAP extensions, X-GM-THRID in particular.

We’ll connect to Gmail using IMAP – remember to use SSL and be sure to enable IMAP protocol for Gmail.

As we plan to search all emails, including sent and received ones, we’ll use CommonFolders class to get ‘All Mail’ folder.

Then we’ll get Envelope for each message. Envelope contains GmailThreadId property. It contains thread id to which this email was assigned to by Gmail.

Finally we’ll find threads that contain only one message – those are messages that were not answered.

// C#
 
using(Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");
    client.Login("pat@gmail.com", "app-password");
 
    // Select 'All Mail' folder.
    List<FolderInfo> folders = client.GetFolders();
    CommonFolders common = new CommonFolders(folders);
    client.Select(common.AllMail);
 
    // Get envelopes for all emails.
    List<long> uids = client.GetAll();
    List<Envelope> envelopes = client.GetEnvelopeByUID(uids);
 
    // Group messages by thread id.
    var threads = new Dictionary<decimal, List<Envelope>>();
    foreach (Envelope envelope in envelopes)
    {
        decimal threadId = (decimal) envelope.GmailThreadId;
        if (threads.ContainsKey(threadId) == false)
            threads[threadId] = new List<Envelope>();
         
        threads[threadId].Add(envelope);
    }
 
    // Find threads containing single message.
    foreach (KeyValuePair<decimal, List<Envelope>> pair in threads)
    {
        if (pair.Value.Count == 1)
        {
            Envelope envelope = pair.Value[0];
            Console.WriteLine("Gmail message id: {0}; subject: {1}", 
                envelope.GmailMessageId , 
                envelope.Subject);
 
        }
    }
    client.Close();
}
' VB.NET
 
Using client As New Imap()
    client.ConnectSSL("imap.gmail.com")
    client.Login("pat@gmail.com", "app-password")
 
    ' Select 'All Mail' folder.
    Dim folders As List(Of FolderInfo) = client.GetFolders()
    Dim common As New CommonFolders(folders)
    client.[Select](common.AllMail)
 
    ' Get envelopes for all emails.
    Dim uids As List(Of Long) = client.GetAll()
    Dim envelopes As List(Of Envelope) = client.GetEnvelopeByUID(uids)
 
    ' Group messages by thread id.
    Dim threads = New Dictionary(Of Decimal, List(Of Envelope))()
    For Each envelope As Envelope In envelopes
        Dim threadId As Decimal = CDec(envelope.GmailThreadId)
        If threads.ContainsKey(threadId) = False Then
            threads(threadId) = New List(Of Envelope)()
        End If
 
        threads(threadId).Add(envelope)
    Next
 
    ' Find threads containing single message.
    For Each pair As KeyValuePair(Of Decimal, List(Of Envelope)) In threads
        If pair.Value.Count = 1 Then
            Dim envelope As Envelope = pair.Value(0)
 
            Console.WriteLine("Gmail message id: {0}; subject: {1}", _
                envelope.GmailMessageId, envelope.Subject)
        End If
    Next
    client.Close()
End Using

The post Search unanswered emails in Gmail first appeared on Blog | Limilabs.

]]>