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
| Sender | Label | Purpose |
|---|---|---|
| billing@vendor-example.com | Vendor/Billing | Groups all billing correspondence from this vendor |
| alerts@monitoring-example.com | Ops/Alerts | Separates automated monitoring alerts from regular mail |
| noreply@hr-example.com | HR/Automated | Isolates 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');
}- Line 2: Defines the map of sender addresses to their target Gmail label names.
- Line 10: Iterates over every configured sender rule in the senderRules object.
- Line 12: Looks up whether the target label already exists before deciding whether to create it.
- Line 17: Builds a search query scoped to a specific sender and a 30-day recency window.
- Line 18: Runs the Gmail search, limited to 50 threads per sender per run to keep execution fast.
- Line 22: Checks each thread's existing labels to avoid reapplying a label it already has.
- Line 27: Applies the label only to threads that don't already carry it, incrementing the run's processed count.
Deploy this example
- 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.
- 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.
- 03
Authorize once
Run the main function from the editor. Accept OAuth scopes when prompted — triggers cannot run until authorization succeeds once.
- 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.