Search code examples
c#.netvb.netgoogle-apigmail

How to retrieve my Gmail messages using Gmail API?


What I want to achieve:


I'm using the Gmail API and basically I would like to connect to my GMail account to read my emails, of INBOX category, and get basic info for each message (title/subject, from, to, date, and the sender).

Problems:


I'm trying to adapt this Google sample, written in C#, to my own needs, I'm searching for a solution in C# or Vb.Net, no matter.

(Be aware that Google shows different code examples for different user-countries, so the code of that webpage maybe will not be the same for every one, that Google's logic really sucks.)

The problems I have with the code below, are these:

  • I'm getting an empty value in lblInbox.MessagesTotal property.
  • msgItem.Raw property is always empty too.
  • I haven't yet discovered how to parse only the messages that are inside the INBOX category.
  • I haven't yet discovered how to determine if a message is read or unread.
  • I haven't yet discovered how to determine the basic info of a message (subject, from, to, date, sender).

This is what I've tried, note that when adapting the Google's sample, I assumed that "user" argument should be the Gmail user account name ("[email protected]"), but I'm not sure it should be that.

Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports System.Text
Imports System.Threading
Imports System.Threading.Tasks

Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Services
Imports Google.Apis.Util.Store
Imports Google.Apis.Gmail
Imports Google.Apis.Gmail.v1
Imports Google.Apis.Gmail.v1.Data
Imports Google.Apis.Gmail.v1.UsersResource

Public Class Form1 : Inherits Form

    Private Async Sub Test() Handles MyBase.Shown
        Await GmailTest()
    End Sub

    Public Async Function GmailTest() As Task
        Dim credential As UserCredential
        Using stream As New FileStream("C:\GoogleAPIKey.json", FileMode.Open, FileAccess.Read)
            credential = Await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
                                                                           {GmailService.Scope.MailGoogleCom},
                                                                           "[email protected]",
                                                                           CancellationToken.None)
        End Using

        ' Create the service.
        Dim service As New GmailService(New BaseClientService.Initializer() With {
             .HttpClientInitializer = credential,
             .ApplicationName = "What I need to put here?"
        })

        ' Get the "INBOX" label/category.
        Dim lblReq As UsersResource.LabelsResource.ListRequest = service.Users.Labels.List("me")
        Dim lblInbox As Data.Label = lblReq.Execute().Labels.Where(Function(lbl) lbl.Name = "INBOX").Single
        Dim msgCount As Integer? = lblInbox.MessagesTotal

        MsgBox("Messages Count: " & msgCount)

        If (msgCount <> 0) Then

            ' Define message parameters of request.
            Dim msgReq As UsersResource.MessagesResource.ListRequest = service.Users.Messages.List("me")

            ' List messages of INBOX category.
            Dim messages As IList(Of Data.Message) = msgReq.Execute().Messages
            Console.WriteLine("Messages:")
            If (messages IsNot Nothing) AndAlso (messages.Count > 0) Then
                For Each msgItem As Data.Message In messages
                    MsgBox(msgItem.Raw)
                Next
            End If

        End If

    End Function

End Class

Question:


I will ask for the most important need (however, any help to solve the other mentioned problems are very welcome):

  • In C# or VB.Net, how can I obtain a collection to iterate all the emails that are in the INBOX group?.

Update:

This is the code that I'm using right now, the intention is to retrieve a collection of all Messages of the specified mailbox label, the problem is that the Payload and Body member of newMsg object is null, so I can't read the email.

What I'm doing wrong?.

Public Async Function GetMessages(ByVal folder As Global.Google.Apis.Gmail.v1.Data.Label) As Task(Of List(Of Global.Google.Apis.Gmail.v1.Data.Message))

    If Not (Me.isAuthorizedB) Then
        Throw New InvalidOperationException(Me.authExceptionMessage)
    Else
        Dim msgsRequest As UsersResource.MessagesResource.ListRequest = Me.client.Users.Messages.List("me")
        With msgsRequest
            .LabelIds = New Repeatable(Of String)({folder.Id})
            .MaxResults = 50
            '.Key = "YOUR API KEY"
        End With

        Dim msgsResponse As ListMessagesResponse = Await msgsRequest.ExecuteAsync()

        Dim messages As New List(Of Global.Google.Apis.Gmail.v1.Data.Message)
        Do While True

            For Each msg As Global.Google.Apis.Gmail.v1.Data.Message In msgsResponse.Messages
                Dim msgRequest As UsersResource.MessagesResource.GetRequest = Me.client.Users.Messages.Get("me", msg.Id)
                msgRequest.Format = MessagesResource.GetRequest.FormatEnum.Full

                Dim newMsg As Message = Await msgRequest.ExecuteAsync()
                messages.Add(newMsg)
            Next msg

            If Not String.IsNullOrEmpty(msgsResponse.NextPageToken) Then
                msgsRequest.PageToken = msgsResponse.NextPageToken
                msgsResponse = Await msgsRequest.ExecuteAsync()
            Else
                Exit Do
            End If

        Loop

        Return messages

    End If

End Function

Solution

  • Currently for some reason or another many of the properties are coming back null from any of the requests. We can still get around that if we have a list of the email ids. We then can use these email ids to send out another request to retrieve further details: from, date, subject, and body. @DalmTo was on the right track as well, but not close enough about the headers as it has changed recently which will require a few more requests.

    private async Task getEmails()
    {
        try
        {
            UserCredential credential;
            using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    // This OAuth 2.0 access scope allows for read-only access to the authenticated 
                    // user's account, but not other types of account access.
                    new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.MailGoogleCom, GmailService.Scope.GmailModify },
                    "NAME OF ACCOUNT NOT EMAIL ADDRESS",
                    CancellationToken.None,
                    new FileDataStore(this.GetType().ToString())
                );
            }
    
            var gmailService = new GmailService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = this.GetType().ToString()
            });
    
            var emailListRequest = gmailService.Users.Messages.List("EMAILADDRESSHERE");
            emailListRequest.LabelIds = "INBOX";
            emailListRequest.IncludeSpamTrash = false;
            //emailListRequest.Q = "is:unread"; // This was added because I only wanted unread emails...
    
            // Get our emails
            var emailListResponse = await emailListRequest.ExecuteAsync();
    
            if (emailListResponse != null && emailListResponse.Messages != null)
            {
                // Loop through each email and get what fields you want...
                foreach (var email in emailListResponse.Messages)
                {
                    var emailInfoRequest = gmailService.Users.Messages.Get("EMAIL ADDRESS HERE", email.Id);
                    // Make another request for that email id...
                    var emailInfoResponse = await emailInfoRequest.ExecuteAsync();
    
                    if (emailInfoResponse != null)
                    {
                        String from = "";
                        String date = "";
                        String subject = "";
                        String body = "";
                        // Loop through the headers and get the fields we need...
                        foreach (var mParts in emailInfoResponse.Payload.Headers)
                        {
                            if (mParts.Name == "Date")
                            {
                                date = mParts.Value; 
                            }
                            else if(mParts.Name == "From" )
                            {
                                from = mParts.Value;
                            }
                            else if (mParts.Name == "Subject")
                            {
                                subject = mParts.Value;
                            }
    
                            if (date != "" && from != "")
                            {
                                if (emailInfoResponse.Payload.Parts == null && emailInfoResponse.Payload.Body != null)
                                {
                                    body = emailInfoResponse.Payload.Body.Data;
                                }
                                else
                                {
                                    body = getNestedParts(emailInfoResponse.Payload.Parts, "");
                                }
                                // Need to replace some characters as the data for the email's body is base64
                                String codedBody = body.Replace("-", "+");
                                codedBody = codedBody.Replace("_", "/");
                                byte[] data = Convert.FromBase64String(codedBody);
                                body = Encoding.UTF8.GetString(data);                               
    
                                // Now you have the data you want...                         
                            }
                        }
                    }                    
                }
            }           
        }
        catch (Exception)
        {
            MessageBox.Show("Failed to get messages!", "Failed Messages!", MessageBoxButtons.OK); 
        }
    }
    
    static String getNestedParts(IList<MessagePart> part, string curr)
    {
        string str = curr;
        if (part == null)
        {
            return str;
        }
        else
        {
            foreach (var parts in part)
            {
                if (parts.Parts  == null)
                {
                    if (parts.Body != null && parts.Body.Data != null)
                    {
                        str += parts.Body.Data;
                    }
                }
                else
                {
                    return getNestedParts(parts.Parts, str);
                }
            }
    
            return str;
        }        
    }
    

    Currently, this method will retrieve all email ids and for each email id get the subject,from, date and body of each email. There are comments throughout the method. If there is something you do not understand, please let me know. On another note: this was tested again before posting this as an answer.