Apps Script example · 8 min read

Label Gmail by Sender: Copy-Paste Apps Script Pattern

Working label gmail by sender example in Apps Script—copy-paste code, common mistakes, and when to get it built professionally.

GmailTime-driven trigger

Sorting incoming email by hand into folders or labels based on who sent it is one of those small daily chores that adds up, and labelGmailBySender automates exactly that by matching sender addresses against a rules object and applying the right label automatically.

Gmail's own filter system can do simple sender-based labeling too, but a script-based approach makes the rules easy to version, extend programmatically, and combine with other logic - like only labeling messages from the last 30 days - that a basic filter can't express as cleanly.

This example defines a small map of sender addresses to label names, searches for recent unlabeled threads from each sender, and applies the corresponding label while avoiding relabeling threads that already have it.

By the end you'll have an inbox that organizes itself by sender on a schedule, with labels created automatically the first time they're needed.

Need this built? Hire a Google Apps Script developer →

Sheet / project setup

SenderLabelPurpose
billing@vendor-example.comVendor/BillingGroups all billing correspondence from this vendor
alerts@monitoring-example.comOps/AlertsSeparates automated monitoring alerts from regular mail
noreply@hr-example.comHR/AutomatedIsolates automated HR notifications for later review

What it does

For each sender-to-label pair defined in senderRules, the function looks up or creates the corresponding Gmail label, searches for threads from that sender within the last 30 days, and applies the label to any thread that doesn't already have it, keeping a running count of how many threads were newly labeled.

  • Labels are created automatically on first use via getUserLabelByName combined with createLabel
  • The search is scoped to the last 30 days to keep each run fast rather than re-scanning the entire mailbox
  • Already-labeled threads are skipped explicitly, avoiding redundant addLabel calls

Prerequisites

The script needs Gmail scopes authorized during the initial run, and the senderRules map should be kept up to date as the set of important senders changes over time.

  • Gmail read and label-modify scopes granted during authorization
  • A senderRules object listing every sender address and its target label name
  • A time-driven trigger calling labelGmailBySender on a regular schedule, such as hourly

Walkthrough

getUserLabelByName returns null rather than throwing when a label doesn't exist yet, which is why the null check followed by createLabel is the correct pattern rather than wrapping the lookup in a try/catch.

The alreadyLabeled check reads each thread's current labels and uses Array.prototype.some to test whether the target label name is already present, preventing wasted addLabel calls on threads that were labeled during a previous run.

Edge cases

A sender address that changes slightly - for example switching from noreply@hr-example.com to notifications@hr-example.com - stops matching immediately and silently, with no error raised, since the search query simply returns zero results for the old address.

  • Threads with messages from multiple senders, where only one matches a rule, still get labeled based on that one matching message being present anywhere in the thread
  • A sender whose messages arrive more than 30 days apart intermittently might be missed if the schedule and window don't overlap generously enough
  • Gmail label names containing a slash, like 'Vendor/Billing', actually create a nested label structure rather than a literal slash in the name

Testing

Send yourself a test email from an address matching one of the senderRules, run the function manually, and confirm both that the label is created (if it didn't exist) and applied to the new thread.

  • Run the function twice in a row and confirm the second run doesn't re-process already-labeled threads unnecessarily
  • Add a new sender rule and confirm its label gets created automatically on the next run
  • Check that threads outside the 30-day window are correctly excluded from each search

Hardening

Because the search window and sender list are both hard-coded, moving them into PropertiesService or a configuration sheet would let the rules be updated without editing and redeploying the script itself.

  • Move senderRules into a configuration sheet so non-developers can add or edit sender-to-label mappings
  • Log a summary of which senders produced how many newly labeled threads on each run, for visibility into rule effectiveness
  • Add a fallback rule that flags completely unmatched but suspicious senders (like ones with generic 'noreply' patterns) for manual review

Variations

The same rule-driven labeling approach extends naturally to matching on subject line keywords or the presence of an attachment, not just the sender address, by adjusting the GmailApp.search query syntax for each rule.

  • Match on subject line keywords instead of, or in addition to, sender address
  • Combine sender-based labeling with archive-gmail-older-than to both label and archive an automated sender's messages after a set period
  • Apply a priority marker label for a shortlist of VIP senders in addition to their category label

Full code: labelGmailBySender()

The function is intentionally data-driven, keeping the actual logic generic and letting the senderRules object define which addresses map to which labels.

function labelGmailBySender() {
  var senderRules = {
    'billing@vendor-example.com': 'Vendor/Billing',
    'alerts@monitoring-example.com': 'Ops/Alerts',
    'noreply@hr-example.com': 'HR/Automated'
  };

  var processedCount = 0;

  for (var senderEmail in senderRules) {
    var labelName = senderRules[senderEmail];
    var label = GmailApp.getUserLabelByName(labelName);
    if (!label) {
      label = GmailApp.createLabel(labelName);
    }

    var query = 'from:' + senderEmail + ' newer_than:30d';
    var threads = GmailApp.search(query, 0, 50);

    for (var i = 0; i < threads.length; i++) {
      var existingLabels = threads[i].getLabels();
      var alreadyLabeled = existingLabels.some(function (l) {
        return l.getName() === labelName;
      });

      if (!alreadyLabeled) {
        threads[i].addLabel(label);
        processedCount++;
      }
    }
  }

  Logger.log(processedCount + ' threads labeled');
}
  1. Line 2: Defines the map of sender addresses to their target Gmail label names.
  2. Line 10: Iterates over every configured sender rule in the senderRules object.
  3. Line 12: Looks up whether the target label already exists before deciding whether to create it.
  4. Line 17: Builds a search query scoped to a specific sender and a 30-day recency window.
  5. Line 18: Runs the Gmail search, limited to 50 threads per sender per run to keep execution fast.
  6. Line 22: Checks each thread's existing labels to avoid reapplying a label it already has.
  7. Line 27: Applies the label only to threads that don't already carry it, incrementing the run's processed count.

Deploy this example

  1. 01

    Open Apps Script

    In the bound spreadsheet: Extensions → Apps Script. For standalone projects, create one at script.google.com and link your Sheet by ID.

  2. 02

    Paste and save

    Add a .gs file, paste the code below, rename constants at the top (sheet names, column letters, API property keys), then save.

  3. 03

    Authorize once

    Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.

  4. 04

    Add the trigger

    Triggers → Add trigger → choose the handler function and event (time-driven, on edit, or on form submit). Delete test triggers before production.

Before scheduling sender-based labeling

  • 1senderRules map reviewed for accuracy and completeness
  • 2Gmail authorization scopes granted during first run
  • 3Label names checked for unintended nested-label slashes
  • 430-day search window confirmed appropriate for expected email frequency
  • 5Time-driven trigger scheduled at a sensible interval, such as hourly
  • 6Considered moving configuration to a sheet for non-developer edits

Frequently asked questions

Scoping the search to a recent window keeps each run fast and well within Apps Script's execution time limits; searching an entire mailbox history on every scheduled run would be slow and mostly redundant since older matching threads were already labeled in previous runs.

Nothing extra is needed - the next scheduled run automatically picks up the new entry from the senderRules object, creates its label if necessary, and starts labeling matching threads from that point forward.

Yes - Gmail search syntax supports domain matching like 'from:vendor-example.com' instead of a full address, so you can key senderRules by domain if you want every address at that domain to receive the same label.

No - addLabel only attaches the label and has no effect on a thread's read or unread status; those are independent properties managed separately through methods like markRead or markUnread.

It can create duplicate label applications if both a native filter and this script target the same sender, though addLabel is idempotent so no harm comes from applying the same label twice - it's just slightly redundant, not broken.

Simply remove that sender's entry from the senderRules object; the label itself and any threads already labeled remain untouched, since deleting the object entry only stops future runs from applying that label to new matching threads.

Related examples

Want this wired into your real workflow?

I adapt these patterns to your Sheet structure, APIs, and triggers — deployed in your Google account. Fixed-scope quotes from $500 · free 30-min consult · quote within 24 hours.