Read and Write Gmail Messages

The version is 9.7.0

In the page Read and Write Gmail Messages Keywords, I found a note by @Hari

Note : Make sure that in the Gmail account you are using with this plugin, ā€œLess secure app accessā€ option is enabled. If this option is not enabled, it throws ā€œusername and password is not acceptedā€ error message.

I missed that part. But right now google is not allowing less secure apps it seems.

Found something here

Do let me know if there is any solution to login via java api and fetch the OTP

Well, I found the workaround for the authentication, but make sure to add below properties.

sysProps.put(ā€œmail.imap.ssl.enableā€, ā€œtrueā€);
sysProps.put(ā€œmail.imap.ssl.protocolsā€, ā€œTLSv1.2ā€);

Also from Gmail we need to

  1. Set 2Factor Auth
  2. Create your app password and use this password in your scripts.

However, now I’m not able to fetch the content of the mail. I’m getting below error

gmail API didn’t work

How do you mean by that?

To read and retrieve an OTP from a Gmail inbox using the Gmail API, you can follow these steps:

  1. Set up the Gmail API credentials and authentication:

    • Create a project in the Google Cloud Console
    • Enable the Gmail API for your project
    • Create OAuth 2.0 credentials (client ID and client secret)
    • Set up the necessary scopes for reading emails
  2. Install the required libraries:

pip install google-auth-oauthlib google-auth-httplib2 google-api-python-client
  1. Use the following Python code to authenticate, search for the OTP email, and extract the OTP:
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
import base64
import re
import os.path

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

def get_gmail_service():
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    
    return build('gmail', 'v1', credentials=creds)

def get_otp_from_email(service):
    results = service.users().messages().list(
        userId='me',
        labelIds=['INBOX'],
        q="subject:OTP is:unread"
    ).execute()

    messages = results.get('messages', [])
    
    if not messages:
        print("No new OTP emails found.")
        return None

    msg = service.users().messages().get(userId='me', id=messages['id']).execute()
    
    payload = msg['payload']
    headers = payload['headers']
    
    for header in headers:
        if header['name'] == 'Subject':
            subject = header['value']
            print(f"Subject: {subject}")

    if 'parts' in payload:
        parts = payload['parts']
        data = parts['body']['data']
    else:
        data = payload['body']['data']
    
    data = data.replace("-","+").replace("_","/")
    decoded_data = base64.b64decode(data)
    
    # Extract OTP using regex (adjust the pattern as needed)
    otp_pattern = r'\b\d{6}\b'  # Assumes OTP is a 6-digit number
    match = re.search(otp_pattern, decoded_data.decode())
    
    if match:
        otp = match.group(0)
        print(f"OTP found: {otp}")
        return otp
    else:
        print("OTP not found in the email body.")
        return None

def main():
    service = get_gmail_service()
    otp = get_otp_from_email(service)
    if otp:
        print(f"Retrieved OTP: {otp}")
    else:
        print("Failed to retrieve OTP.")

if __name__ == '__main__':
    main()

This script does the following:

  1. Authenticates with the Gmail API using OAuth 2.0
  2. Searches for unread emails in the inbox with ā€œOTPā€ in the subject
  3. Retrieves the most recent matching email.
  4. Extracts the email body and decodes it from base64
  5. Uses a regular expression to find a 6-digit OTP in the email body

To use this script:

  1. Place your credentials.json file (obtained from Google Cloud Console) in the same directory as the script
  2. Run the script. It will open a browser window for authentication on first run
  3. The script will then search for the most recent OTP email and attempt to extract the OTP

Above is from one of the LLM responses

Java code

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.ListMessagesResponse;
import com.google.api.services.gmail.model.Message;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GmailOTPRetriever {
    private static final String APPLICATION_NAME = "Gmail OTP Retriever";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    private static final String TOKENS_DIRECTORY_PATH = "tokens";

    private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_READONLY);
    private static final String CREDENTIALS_FILE_PATH = "/credentials.json";

    private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
        InputStream in = GmailOTPRetriever.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
        if (in == null) {
            throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
        }
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
                .setAccessType("offline")
                .build();
        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    }

    public static void main(String... args) throws IOException, GeneralSecurityException {
        final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
        Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
                .setApplicationName(APPLICATION_NAME)
                .build();

        String otp = getOTPFromEmail(service);
        if (otp != null) {
            System.out.println("Retrieved OTP: " + otp);
        } else {
            System.out.println("Failed to retrieve OTP.");
        }
    }

    private static String getOTPFromEmail(Gmail service) throws IOException {
        String user = "me";
        ListMessagesResponse listResponse = service.users().messages().list(user)
                .setQ("subject:OTP is:unread")
                .setMaxResults(1L)
                .execute();

        List<Message> messages = listResponse.getMessages();
        if (messages == null || messages.isEmpty()) {
            System.out.println("No new OTP emails found.");
            return null;
        }

        String messageId = messages.get(0).getId();
        Message message = service.users().messages().get(user, messageId).execute();

        String subject = "";
        for (Message.MessagePartHeader header : message.getPayload().getHeaders()) {
            if (header.getName().equals("Subject")) {
                subject = header.getValue();
                System.out.println("Subject: " + subject);
                break;
            }
        }

        String emailBody = new String(org.apache.commons.codec.binary.Base64.decodeBase64(
                message.getPayload().getParts().get(0).getBody().getData()));

        Pattern otpPattern = Pattern.compile("\\b\\d{6}\\b");
        Matcher matcher = otpPattern.matcher(emailBody);

        if (matcher.find()) {
            String otp = matcher.group(0);
            System.out.println("OTP found: " + otp);
            return otp;
        } else {
            System.out.println("OTP not found in the email body.");
            return null;
        }
    }
}

For Maven, add these to your pom.xml:

<dependencies>
  <dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client</artifactId>
    <version>1.32.1</version>
  </dependency>
  <dependency>
    <groupId>com.google.oauth-client</groupId>
    <artifactId>google-oauth-client-jetty</artifactId>
    <version>1.32.1</version>
  </dependency>

Yea, I actually covered this in this blog post last year

That LLM answer looks pretty similar to what I was talking about back then…

1 Like