Skip to content
Bruno Sonnino
Menu
  • Home
  • About
Menu

Deleting large emails in GMail with the GMail API

Posted on 1 October 2022

Introduction

I am a long time user of Gmail, and I usually don't delete any email, I just archive the emails after reading and processing them, to keep my inbox clean.

Last week, I got a notice from Gmail that I was reaching the 15GB free limit and, in order to continue receiving emails, I should I should either buy some extra storage or clean my mail archive.

I know that I store a lot of garbage there, so I decided to clean the archive: the first step was to delete some old newsletters, and some junk email, but this didn't even scratch the size of my mailbox (maybe it removed about 200Mb of data).

Then I started to use Gmail's size filters: if you enter "larger:10M" in the query box, Gmail will show only messages with 10Mb or more. This is a great improvement, but there are two gotchas here: the messages aren't sorted by size and you don't know the size of every message.

That way, you won't be able to effectively clean your mailbox – it will be a very difficult task to search among your 1500 messages which ones are good candidates to delete. So I decided to bite the bullet and create a C# program to scan my mailbox, list the largest ones and delete some of them. I decided to create a Universal Windows Platform app, so I could use on both my desktop and Windows Phone with no changes in the app code.

Registering the app with Google

The first step to create the app is to register it with Google, so you can get an app id to use in your app. Go to https://console.developers.google.com/flows/enableapi?apiid=gmail and create a new project. Once you have registered, you must get the credentials, to use in the app.

Figure 1 – Adding credentials

This will create new credentials for your app, which you must download and add to your project. When you download the credentials, you get a file named client_id.json , which you will include in your project. The next step is to create the project.

Creating the project

You must go to Visual Studio and create a new UWP app (blank app).

Figure 2 – Creating a new UWP app

A dialog appears asking you the target version for the app, and you can click OK. Then, you must add the NuGet package Google.Apis.Gmail.v1. This can be done in two ways: in the Solution Explorer, right click in the "References" node and select "Manage NuGet Packages" and search for Gmail, adding the Gmail package.

The second way is to open the Package Manager Console Window and adding the command:

Install-Package Google.Apis.Gmail.v1
PowerShell

Once you have installed the package, you must add the json file with the credentials to your project. Right click the project and select Add/Existing item and add the client_id.json file. Go to the properties window and select Build Action to Content and Copy to Output Directory as Copy always.

Getting User authorization

The first thing you must do in the program is to get the user authorization to access the email. This is done using OAuth2, with this code:

public async Task<UserCredential> GetCredential()
{
    var scopes = new[] { GmailService.Scope.GmailModify };
    var uri = new Uri("ms-appx:///client_id.json");
    _credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        uri, scopes, "user", CancellationToken.None);
    return _credential;
}
C#

We call the AuthorizeAsync method of GoogleWebAuthorizationBroker, passing the uri for client_id.json , andthe scope we want (modify emails). You can call this method in the constructor of MainPage:

public MainPage()
{
    this.InitializeComponent();
    GetCredential();
}
C#

When you run the program, it will open a web page to get the user's authorization. This procedure doesn't store any password in the application, thus making it safe for the users: they can give their credentials to the Google broker and the broker will send just an authorization token to access the email.

Figure 3 – Web page for authorization

Figure 4 – Authorization consent

If you take a look at Figure 4, you will see that we are not asking for permission to delete the mail. We won't need this permission, because we are going to just move the messages to trash. Then, the user will be able to review the messages and delete them permanently.

Getting emails

Once you have the authorization, you can get the emails from the server. In MainPage.xaml, add the UI for the application:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Button Content ="Get Messages" Click="GetMessagesClick" 
            HorizontalAlignment="Right" Margin="5" Width="120"/>
    <ListView Grid.Row="1" x:Name="MessagesList" />
    <TextBlock Grid.Row="2" x:Name="CountText" Margin="5"/>
</Grid>
XML

We will have a button to get the messages and add them to the listview. At the bottom, a textblock will display the message count. The code for retrieving the messages is:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    UsersResource.MessagesResource.ListRequest request =
    service.Users.Messages.List("me");
    request.Q = "larger:5M";
    request.MaxResults = 1000;
    messages = request.Execute().Messages;
    MessagesList.ItemsSource = messages;
    CountText.Text = $"{messages.Count} messages";
}
C#

We create a request for getting the messages larger than 5Mb and returning a maximum of 1000 results. If you have more than 1000 emails larger than 5Mb, there's no guarantee you will get the largest emails, but you can change the query settings to satisfy your needs. Then, we query the server and fill the listview. If you run the app and click the button, you will see something like in Figure 5:

Figure 5 – Mail results displayed in the app window

We see only the item type because we didn't set up an item template. That can be done in MainPage.xaml:

<ListView Grid.Row="1" x:Name="MessagesList" >
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Id}"/>
                <TextBlock Text="{Binding Snippet}" FontWeight="Bold"/>
                <TextBlock Text="{Binding SizeEstimate}"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>
XML

Running the app, you will see that the list only shows the Ids for the messages. This first call doesn't return the full message. To get the message contents, we must do a second call, to retrieve the message contents:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });

        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        var sizeEstimate = 0L;
        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
        }
    });
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}
C#

When we are getting the messages, we limit the data recovered. The default behavior for the Get request is to retrieve the full message, but this would be an overkill. We only get the Subject, Date and From headers for the message. If you run the app, you will get the snippet and the size, but you will see that the app hangs while retrieving the messages. This is not a good thing to do. We must get the messages in the background:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;

    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;

        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
        }
    });
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}
C#

Now the code doesn't block the UI, but there's no indication of what's happening. Let's add a progress bar to the UI:

<TextBlock Grid.Row="2" x:Name="CountText" Margin="5"/>
<Border x:Name="BusyBorder" Grid.Row="0" Grid.RowSpan="3" 
        Background="#40000000" Visibility="Collapsed">
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBlock Text="downloading messages" x:Name="OperationText"/>
        <ProgressBar x:Name="ProgressBar" Margin="0,5"/>
        <TextBlock x:Name="DownloadText"  HorizontalAlignment="Center"/>
    </StackPanel>
</Border>
XML

To update the progress bar while downloading, we use this code:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;

    BusyBorder.Visibility = Visibility.Visible;
    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            ProgressBar.Maximum = messages.Count);

        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;

            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messages.Count}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}
C#

We set the visibility of the Busy border to visible before downloading the messages. As we download the messages, we update the progress bar. We are running the code in a background thread, so we can't update the progress bar and the text directly, we must use the Dispatcher to update the controls in the main thread. Now, when we run the code, the busy border is shown and the progress bar is updated with the download count. At the end, we get the messages, with the snippets and size.

Figure 6 – Message results

You can see some problems in this display:

  • It's far from good, and should be improved
  • It doesn't show the subject, date and who sent the message
  • The snippet format is encoded and should be decoded
  • The size could be formatted

We can fix these issues by creating a new class:

public class EmailMessage
{
    public string Id { get; set; }
    public bool IsSelected { get; set; }
    public string Snippet { get; set; }
    public string SizeEstimate { get; set; }
    public string From { get; set; }
    public string Date { get; set; }
    public string Subject { get; set; }
}
C#

And use it, instead of the Message class:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;
    var emailMessages = new List<EmailMessage>();
    OperationText.Text = "downloading messages";
    BusyBorder.Visibility = Visibility.Visible;
    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            ProgressBar.Maximum = messages.Count);

        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
            emailMessages.Add(new EmailMessage()
            {
                Id = messages[index].Id,
                Snippet = WebUtility.HtmlDecode(messages[index].Snippet),
                SizeEstimate = $"{messages[index].SizeEstimate:n0}",
                From = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "From").Value,
                Subject = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "Subject").Value,
                Date = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "Date").Value,
            });
            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messages.Count}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
    MessagesList.ItemsSource = new ObservableCollection<EmailMessage>(
        emailMessages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}
C#

With this new code, we can change the item template, to show the new data:

<ListView.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="40"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <CheckBox HorizontalAlignment="Left" VerticalAlignment="Center" 
                      IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="5"/>
            <StackPanel Grid.Column="1" Margin="5">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="From:" Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding From}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Date:" Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding Date}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Size: " Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding SizeEstimate}"/>
                </StackPanel>
                <TextBlock Text="{Binding Subject}"/>
                <TextBlock Text="{Binding Snippet}" FontWeight="Bold"/>
            </StackPanel>
            <Rectangle Grid.Column="0" Grid.ColumnSpan="2" 
                       HorizontalAlignment="Stretch" VerticalAlignment="Bottom" 
                       Height="1" Fill="Black"/>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>
XML

With this code, we get a result like this:

Figure 7 – Message results with more data

We could still improve the performance of the app by making multiple requests for messages at the same time, but I leave this for you.

Deleting emails from the server

Now, the only task that remains is to delete the emails from the server. For that, we must add another button:

<Button Content ="Get Messages" Click="GetMessagesClick" 
        HorizontalAlignment="Right" Margin="5" Width="120"/>
<Button Grid.Row="0" Content ="Delete Messages" Click="DeleteMessagesClick" 
        HorizontalAlignment="Right" Margin="5,5,140,5" Width="120"/>
XML

The code for deleting messages is this:

private async void DeleteMessagesClick(object sender, RoutedEventArgs e)
{
    var messages = (ObservableCollection<EmailMessage>) MessagesList.ItemsSource;
    var messagesToDelete = messages.Where(m => m.IsSelected).ToList();
    if (!messagesToDelete.Any())
    {
        await (new MessageDialog("There are no selected messages to delete")).ShowAsync();
        return;
    }
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    OperationText.Text = "deleting messages";
    ProgressBar.Maximum = messagesToDelete.Count;
    DownloadText.Text = "";
    BusyBorder.Visibility = Visibility.Visible;
    var sizeEstimate = messages.Sum(m => Convert.ToInt64(m.SizeEstimate));
    await Task.Run(async () =>
    {
        for (int index = 0; index < messagesToDelete.Count; index++)
        {
            var message = messagesToDelete[index];
            var response = service.Users.Messages.Trash("me", message.Id);
            response.Execute();
            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messagesToDelete.Count}";
                messages.Remove(message);
                sizeEstimate -= Convert.ToInt64(message.SizeEstimate);
                CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
}
C#

If you run the app, you can select the messages you want and delete them. They will be moved to the trash folder, so you can double check the messages before deleting them.

Conclusion

This article has shown how to access the Gmail messages using the Gmail API and delete the largest messages, all in your Windows 10 app. Oh, and did I mention that the same app works also in Windows Phone, or in other Windows 10 devices?

All the source code for this article is available on GitHub, at http://github.com/bsonnino/LargeEmailsGmail

This article was first published at https://learn.microsoft.com/en-us/archive/blogs/mvpawardprogram/accessing-and-deleting-large-e-mails-in-gmail-with-c

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

  • May 2025
  • December 2024
  • October 2024
  • August 2024
  • July 2024
  • June 2024
  • November 2023
  • October 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • November 2022
  • October 2022
  • September 2022
  • August 2022
  • June 2022
  • April 2022
  • March 2022
  • February 2022
  • January 2022
  • July 2021
  • June 2021
  • May 2021
  • April 2021
  • March 2021
  • February 2021
  • January 2021
  • December 2020
  • October 2020
  • September 2020
  • April 2020
  • March 2020
  • January 2020
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • April 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • November 2017
  • October 2017
  • September 2017
  • August 2017
  • June 2017
  • May 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • July 2016
  • June 2016
  • May 2016
  • April 2016
  • March 2016
  • February 2016
  • October 2015
  • August 2013
  • May 2013
  • February 2012
  • January 2012
  • April 2011
  • March 2011
  • December 2010
  • November 2009
  • June 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • July 2008
  • March 2008
  • February 2008
  • January 2008
  • December 2007
  • November 2007
  • October 2007
  • September 2007
  • August 2007
  • July 2007
  • Development
  • English
  • Português
  • Uncategorized
  • Windows

.NET AI Algorithms asp.NET Backup C# Debugging Delphi Dependency Injection Desktop Bridge Desktop icons Entity Framework JSON Linq Mef Minimal API MVVM NTFS Open Source OpenXML OzCode PowerShell Sensors Silverlight Source Code Generators sql server Surface Dial Testing Tools TypeScript UI Unit Testing UWP Visual Studio VS Code WCF WebView2 WinAppSDK Windows Windows 10 Windows Forms Windows Phone WPF XAML Zip

  • Entries RSS
  • Comments RSS
©2025 Bruno Sonnino | Design: Newspaperly WordPress Theme
Menu
  • Home
  • About