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
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;
}
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();
}
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>
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";
}
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>
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}";
}
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}";
}
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>
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}";
}
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; }
}
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}";
}
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>
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"/>
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;
}
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