How to Build a Chrome Extension to Save Gmail Emails Using the Gmail API

In this post, I’ll walk you through how to build a Chrome Extension that connects to your Gmail account, reads emails, and sends the email content (subject, body, and attachments) to an external API—like a CRM, file management tool, or custom backend.

This is a real-world use case from one of my recent projects: a Gmail extension that sends emails and documents to a system called Exchange Center.

Let’s dive in. 🚀

Tools & Prerequisites

Before we begin, here’s what you’ll need:

  • A Google Developer Project with Gmail API enabled
  • Your OAuth Client ID
  • Basic knowledge of JavaScript / React (optional but helpful)
  • Familiarity with Chrome Extensions (Manifest V3)

Step 1: Set Up Your Chrome Extension

Create the base extension folder with the following files:

manifest.json

{
  "manifest_version": 3,
  "name": "Gmail Saver",
  "version": "1.0",
  "description": "Save Gmail emails to an external system.",
  "permissions": ["identity", "scripting"],
  "host_permissions": ["https://mail.google.com/", "https://www.googleapis.com/*"],
  "action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  },
  "oauth2": {
    "client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
    "scopes": ["https://www.googleapis.com/auth/gmail.readonly"]
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["https://mail.google.com/*"],
      "js": ["content.js"],
      "run_at": "document_idle"
    }
  ]
}

Step 2: Authenticate with Google

In your background.js, use chrome.identity.getAuthToken() to get the access token:

chrome.runtime.onInstalled.addListener(() => {
  console.log("Gmail Saver Extension Installed");
});

function authenticate(callback) {
  chrome.identity.getAuthToken({ interactive: true }, function (token) {
    if (chrome.runtime.lastError) {
      console.error(chrome.runtime.lastError);
      return;
    }
    callback(token);
  });
}

Step 3: Fetch Emails from Gmail API

Use the Gmail API to get a list of messages and read their contents.

function fetchEmails(token) {
  fetch("https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=5", {
    headers: { Authorization: `Bearer ${token}` }
  })
    .then((res) => res.json())
    .then((data) => {
      data.messages.forEach((msg) => {
        fetch(`https://www.googleapis.com/gmail/v1/users/me/messages/${msg.id}`, {
          headers: { Authorization: `Bearer ${token}` }
        })
          .then((res) => res.json())
          .then((messageDetail) => {
            const subjectHeader = messageDetail.payload.headers.find(
              (h) => h.name === "Subject"
            );
            const subject = subjectHeader ? subjectHeader.value : "(No Subject)";
            const body = getEmailBody(messageDetail.payload);
            console.log("Subject:", subject);
            console.log("Body:", body);
          });
      });
    });
}

function getEmailBody(payload) {
  const encodedBody =
    payload.parts?.[0]?.body?.data || payload.body?.data || "";
  return decodeURIComponent(escape(window.atob(encodedBody.replace(/-/g, "+").replace(/_/g, "/"))));
}

Step 4: Send Email Content to Your API

Once you have the subject and body, send it to your backend system:

function sendToExchangeCenter(subject, body) {
  fetch("https://your-api.com/save-email", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ subject, body })
  })
    .then((res) => res.json())
    .then((response) => console.log("Saved to system", response))
    .catch((err) => console.error("Save failed", err));
}

Step 5: Inject Button into Gmail UI

Let’s make this more user-friendly by adding a button directly into Gmail threads.

In content.js:

function addSaveButton() {
  const container = document.querySelector("div[aria-label='More']");
  if (!container || document.getElementById("saveEmailButton")) return;

  const btn = document.createElement("button");
  btn.innerText = "Save Email";
  btn.id = "saveEmailButton";
  btn.style = "margin-left: 10px; background: #1a73e8; color: white; padding: 6px 12px; border: none; border-radius: 4px;";

  btn.onclick = () => {
    chrome.runtime.sendMessage({ type: "SAVE_EMAIL" });
  };

  container.parentNode.appendChild(btn);
}

setInterval(addSaveButton, 3000);

Handle the message in your background.js:

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === "SAVE_EMAIL") {
    authenticate((token) => {
      fetchEmails(token);
    });
  }
});

Step 6: Security & API Considerations

  • Use HTTPS for your API
  • Never store Gmail access tokens long-term on the client
  • Respect Google’s API usage limits and user privacy

Conclusion

You now have a fully working Chrome Extension that:

  • Authenticates using Gmail OAuth
  • Reads and parses Gmail content
  • Sends data to your backend system
  • Injects a save button into the Gmail UI

This can be extended to support:

  • Attachments
  • Filtering by labels
  • Batch processing
  • CRM integration

📣 Need Help with a Custom Gmail Extension?

I specialize in building Chrome extensions, custom email automation, and full-stack tools for businesses. If you’re looking to streamline your email workflows — contact me here: https://navaneethm.in/get-in-touch/

Resolve the Access Denied Error in Amazon S3 when viewing uploaded file

Before you can access a file that is uploaded to a bucket, you need to apply public permissions using either “ACL settings” or the “bucket policy” tool.

To apply the bucket policy, perform the following steps:
1) Open S3 management console https://console.aws.amazon.com/s3/
2) Choose a bucket, click “Properties”, click “Add bucket policy”.
3) Apply the policy using the policy script or AWS policy generator.
4) The following example policy will grant permissions for all user who got the link to view file located in the examplebucket.

{
  "Version": "2008-10-17",
  "Id": "Policy-id",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::examplebucket/*",
      "Condition": {}
    }
  ]
} 

For more information, please see the following AWS documentation:
http://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html
http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html

Change MySql root password on centos

To change the root password, we need to still start mySQL with –skip-grant-tables options and update the user table

1. Stop mysql:
systemctl stop mysqld

2. Set the mySQL environment option
systemctl set-environment MYSQLD_OPTS="--skip-grant-tables"

3. Start mysql usig the options you just set
systemctl start mysqld

4. Login as root
mysql -u root

5. Update the root user password with these mysql commands
mysql->UPDATE mysql.user SET authentication_string = PASSWORD('MyNewPassword')
    ->WHERE User = 'root' AND Host = 'localhost';
mysql-> FLUSH PRIVILEGES;
mysql-> quit

*** Edit ***
For 5.7.6 and later, you should use
   mysql->ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass';
Or you'll get a warning

6. Stop mysql
systemctl stop mysqld

7. Unset the mySQL envitroment option so it starts normally next time
systemctl unset-environment MYSQLD_OPTS

8. Start mysql normally:
systemctl start mysqld

Try to login using your new password:
7. mysql -u root -p

Redirecting to HTTPS from HTTP

If we have added an SSL certificate to your domain, we can force all visits to your site to use HTTPS to ensure your traffic is secure. This post shows examples on how to do this.

The following forces any http request to be rewritten using https. For example, the following code forces a request to http://example.com to load https://example.com. It also forces directly linked resources (images, css, etc.) to use https:

RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] 

Hope this help you to redirect from http to https