Skip to content

Instantly share code, notes, and snippets.

@maurigamg
Last active April 24, 2025 22:25
Show Gist options
  • Save maurigamg/07f48e4a70edcafdfc338a9ed7fdb614 to your computer and use it in GitHub Desktop.
Save maurigamg/07f48e4a70edcafdfc338a9ed7fdb614 to your computer and use it in GitHub Desktop.
Gmail API: Using the gmail pkg for Go

A simple main.go that creates a gmail service to search and download attachments from gmail.

Description

This main.go was created using the gmail package for Golang and following this quickstart guide, which describes how to set up the Gmail API and authenticate with it.

For more information about the Gmail API, you can check out the official documentation.

Scopes

Scopes are required when creating the token because they are used to define the level of access of the application. If you want to use different scopes, you will need to create a new token.

For this main.go, I used the readonly scope (https://www.googleapis.com/auth/gmail.readonly) because I only wanted to read messages.

Here is a list of all the scopes that can be used with the Gmail API.

Search Operators

They are used to set the query for filtering the messages. Here is a list of all the search operators that you can use.

Notes

  • If you want to filter messages by specific time and not just by date, you will have to use the after and before operators in epoch time format.
package main
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
"google.golang.org/api/option"
)
// userID is the user's email address. The special value "me" can be used to indicate the authenticated user.
// https://developers.google.com/gmail/api/reference/rest/v1/users.messages/get
const userID = "me"
var (
// destinationDir is the path to the directory where the attachments will be saved.
destinationDir = os.Getenv("DESTINATION_DIR")
// tokenFile is the path to the file containing the token.
tokenFile = os.Getenv("CLIENT_TOKEN_FILE")
)
// getAttachments retrieves the attachments from the messages matching the query
// and saves them to the destination directory.
func getAttachments(ctx context.Context, svc *gmail.Service, query string) {
pageToken := ""
for {
// Listing messages.
listResp, err := svc.Users.Messages.List(userID).Q(query).PageToken(pageToken).Context(ctx).Do()
if err != nil {
log.Fatalf("Unable to retrieve messages: %v", err)
}
log.Printf("Processing %d message(s)...\n", len(listResp.Messages))
for _, msgInfo := range listResp.Messages {
// Getting a message.
log.Printf("Processing msg %s...\n", msgInfo.Id)
msg, err := svc.Users.Messages.Get(userID, msgInfo.Id).Context(ctx).Do()
if err != nil {
log.Fatalf("Unable to retrieve message: %v", err)
}
for _, part := range msg.Payload.Parts {
if part.Filename == "" { // Only present for attachments
continue
}
// Getting an attachment.
att, err := svc.Users.Messages.Attachments.Get(userID, msg.Id, part.Body.AttachmentId).Context(ctx).Do()
if err != nil {
log.Fatalf("Unable to retrieve attachment: %v", err)
}
// Saving the attachment.
saveAttachment(att.Data, part.Filename)
}
}
if pageToken = listResp.NextPageToken; pageToken == "" {
break
}
}
}
// getTokenFromWeb requests a token from the web, then returns the retrieved token.
func getTokenFromWeb(ctx context.Context, config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the authorization code: \n%v\n", authURL)
var authCode string
if _, err := fmt.Scan(&authCode); err != nil {
log.Fatalf("Unable to read authorization code: %v", err)
}
tok, err := config.Exchange(ctx, authCode)
if err != nil {
log.Fatalf("Unable to retrieve token from web: %v", err)
}
return tok
}
// saveAttachment saves the attachment to the destination directory.
func saveAttachment(data, filename string) {
// Decoding the base64url encoded string into bytes.
b, err := base64.URLEncoding.DecodeString(data)
if err != nil {
log.Fatalf("Unable to decode data: %v", err)
}
// Creating a file for saving the attachment.
log.Printf("Saving %s...\n", filename)
out, err := os.Create(destinationDir + filename)
if err != nil {
log.Fatalf("Unable to create file: %v", err)
}
defer func() {
if err = out.Close(); err != nil {
log.Fatalf("Unable to close the file: %v", err)
}
}()
if _, err = out.Write(b); err != nil {
log.Fatalf("Unable to write file: %v", err)
}
if err = out.Sync(); err != nil {
log.Fatalf("Unable to sync commit: %v", err)
}
}
// saveToken saves a token to a file path.
func saveToken(token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", tokenFile)
f, err := os.OpenFile(tokenFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer func() {
if err = f.Close(); err != nil {
log.Fatalf("Unable to close the token file: %v", err)
}
}()
if err = json.NewEncoder(f).Encode(token); err != nil {
log.Fatalf("Unable to encode token: %v", err)
}
}
// tokenFromFile returns the token, which was previously saved into a file.
func tokenFromFile() *oauth2.Token {
f, err := os.Open(tokenFile)
if err != nil {
log.Fatalf("Unable to open token file: %v", err)
}
defer func() {
if err = f.Close(); err != nil {
log.Fatalf("Unable to close the token file: %v", err)
}
}()
tok := &oauth2.Token{}
if err = json.NewDecoder(f).Decode(tok); err != nil {
log.Fatalf("Unable to decode token: %v", err)
}
return tok
}
func main() {
// Setting up the config manually. You can also retrieve a config from a file, e.g.
// config, err := google.ConfigFromJSON(b, gmail.GmailReadonlyScope)
// where b corresponds to the contents of the client credentials file.
config := &oauth2.Config{
ClientID: os.Getenv("CLIENT_ID"),
ClientSecret: os.Getenv("CLIENT_SECRET"),
Endpoint: google.Endpoint,
RedirectURL: "http://localhost", // Not required once you have a token
Scopes: []string{gmail.GmailReadonlyScope},
}
// Retrieving a token for first time and saving it.
// Required if you don't have a token, or it's expired.
// saveToken(getTokenFromWeb(ctx, config))
// Setting up the token manually. You can also retrieve a token from a file, e.g.
// token := tokenFromFile()
token := &oauth2.Token{
AccessToken: os.Getenv("ACCESS_TOKEN"),
TokenType: "Bearer",
RefreshToken: os.Getenv("REFRESH_TOKEN"),
Expiry: time.Date(2023, time.March, 27, 15, 5, 14, 0, time.UTC),
}
// Creating the gmail service.
ctx := context.Background()
svc, err := gmail.NewService(ctx, option.WithHTTPClient(config.Client(ctx, token)))
if err != nil {
log.Fatalf("Unable to create gmail service: %v", err)
}
// Getting the attachments.
query := "from:[email protected] has:attachment subject:(My Subject)"
getAttachments(ctx, svc, query)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment