A Google Apps Script that audits your Google Contacts and sends you email reports about data quality, upcoming birthdays, duplicates, and more.
Reports are sent to yourself β think of it as a personal contacts health check that runs on autopilot.
| Report | What it tells you |
|---|---|
| π Upcoming Birthdays | Who has a birthday in the next N days, with age countdown |
| π Duplicate Contacts | Groups of contacts that look like duplicates (by name, email, or phone) |
| π Contact Overview | Stats dashboard β completeness, top cities, birthday distribution |
| π·οΈ Label Overview | Label distribution, most/least used labels, and unlabeled contacts |
| π Missing Info | Contacts missing email, phone, city, or birthday |
| π§ Data Quality | Missing surnames, invalid phones, shared numbers, empty contacts, formatting issues |
| Action | What it does |
|---|---|
| π·οΈ Auto-Labeling | Assign labels based on rules (email domain, city, name patterns, regex) |
| βοΈ Name Formatter | Fix capitalization, trim spaces, swap "Last, First" format |
| π± Phone Normalizer | Convert local numbers to international format with consistent spacing |
| πΈ Instagram β Website | Convert @handles in notes to clickable website fields |
| π¬ Messenger β Website | Convert FB/Messenger usernames in notes to m.me website fields |
All actions support dryRun mode β preview changes without modifying contacts.
π‘ Looking for automatic birthday calendar events? Check out birthday-calendar-sync.
- Go to script.google.com and create a new project
- Or use
claspto push from this repo (see Local development)
In the Apps Script editor, go to Services (+ icon) and enable:
- People API (v1)
- Gmail API (v1)
Copy src/config.js.template to src/config.js and adjust the settings.
Run setupSchedules() once from the Apps Script editor. It creates a single daily trigger that runs reports and actions on their configured days.
Re-run setupSchedules() any time you change schedules. It cleanly replaces existing triggers.
The first run will ask for permissions. The script needs access to:
- Your contacts (read + write for actions)
- Gmail (to send yourself reports)
- Drive (to read the script's own name for the sender field)
All options live in src/config.js. The config has three sections:
const generalConfig = {
useLabel: false, // only report on contacts with specific labels
labelFilter: [], // e.g. ['Friends', 'Family']
excludeLabels: [], // always exclude from all reports
sortContactsBy: 'name', // 'name' | 'name-desc' | 'labels' | 'city'
maxContactsPerReport: 0, // 0 = unlimited
includeEditLinks: true, // add "edit" links in emails
includeWhatsAppLinks: false, // add WhatsApp links next to phone numbers
birthdayFormat: 'dd.MM.', // 'dd.MM.' | 'dd/MM' | 'MM/dd' | 'dd MMM' | 'MMM dd'
};Each report has its own config block with schedule, email subject, and report-specific settings:
const reports = {
upcomingBirthdays: {
schedule: 'weekly', // 'daily' | 'weekly' | 'monthly' | 'off'
day: 2, // 1β7 for weekly (1=Sun, 2=Mon, ..., 7=Sat)
emailSubject: 'π Upcoming Birthdays',
aheadDays: 14, // days to look ahead (1β365)
showAge: true,
},
duplicates: {
schedule: 'monthly',
day: 1, // 1β28 for monthly (day of month)
emailSubject: 'π Duplicate Contacts',
matchFields: ['name', 'email', 'phone'],
},
contactOverview: {
schedule: 'monthly',
day: 1,
emailSubject: 'π Contact Overview',
},
labelOverview: {
schedule: 'monthly',
day: 1,
emailSubject: 'π·οΈ Label Overview',
},
missingInfo: {
schedule: 'monthly',
day: 1,
emailSubject: 'π Missing Info',
fields: ['email', 'phone', 'birthday', 'city'],
},
dataQuality: {
schedule: 'monthly',
day: 1,
emailSubject: 'π§ Data Quality',
phoneRegex: /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/,
},
};Each action has schedule, dry run, and action-specific settings in one block:
const actions = {
autoLabeling: {
schedule: 'monthly',
day: 1,
dryRun: false, // true = preview only, no changes
sendReport: true, // send summary email
emailSubject: 'π·οΈ Auto-Labeling Summary',
rules: [
{ field: 'email', contains: '@company.com', label: 'Work' },
{ field: 'city', equals: 'berlin', label: 'π Berlin' },
{ field: 'name', matches: '\\(swing\\)', label: 'Swing' },
],
},
nameFormatter: {
schedule: 'monthly',
day: 5,
dryRun: false,
sendReport: true,
emailSubject: 'βοΈ Name Formatter Summary',
swapLastFirst: true, // "Last, First" β "First Last"
},
phoneNormalizer: {
schedule: 'monthly',
day: 10,
dryRun: true, // preview first!
sendReport: true,
emailSubject: 'π± Phone Normalizer Summary',
defaultCountryCode: '+49',
phoneRegex: /^[+]?[(]?[0-9]{1,4}[)]?[-\s./0-9]*$/,
},
instagramToWebsite: {
schedule: 'monthly',
day: 15,
dryRun: false,
sendReport: true,
emailSubject: 'πΈ Instagram β Website Summary',
},
messengerToWebsite: {
schedule: 'monthly',
day: 15,
dryRun: false,
sendReport: true,
emailSubject: 'π¬ Messenger β Website Summary',
},
};A single daily trigger handles all schedules:
'daily'β runs every day'weekly'β runs on the configuredday(1=Sunday, 2=Monday, ..., 7=Saturday)'monthly'β runs on the configuredday(1β28, day of month)'off'β disabled
Actions are spread across different days to avoid API quota limits.
Rule conditions: contains, equals, startsWith, endsWith, matches (regex).
All case-insensitive. Fields: email, phone, city, name.
You can run any report or action from the Apps Script editor:
Reports:
sendUpcomingBirthdaysReport()β or pass days:sendUpcomingBirthdaysReport(14)sendDuplicateContactsReport()sendContactOverviewReport()sendLabelOverviewReport()sendMissingInfoReport()sendDataQualityReport()sendAllReports()β runs all enabled reports
Actions:
runAutoLabeling()runNameFormatter()runPhoneNormalizer()runInstagramToWebsite()runMessengerToWebsite()
Utility:
setupSchedules()β create/update triggersremoveSchedules()β remove all managed triggersvalidateConfig()β check config for errorstestContacts()β fetch and log all contact namestestLabels()β fetch and log all labelsgetHealthStatus()β returns contact/label counts and response time
This project uses clasp for local development and deployment.
# Install dependencies
pnpm install
# Set up clasp
cp .clasp.json.template .clasp.json
# Edit .clasp.json and add your script ID
# Push to Apps Script
pnpm run deploy # runs tests first, then pushes
# Run tests locally
pnpm testsrc/
βββ _setup.js # Schedule management (setupSchedules, removeSchedules)
βββ actions.js # Contact actions (auto-label, name format, phone normalize, social β website)
βββ config.js # Your config (not committed)
βββ config.js.template # Config template with all options documented
βββ contact.js # Contact class
βββ contact_manager.js # Fetching, filtering, and querying contacts
βββ email_manager.js # Email formatting and sending
βββ label_manager.js # Label fetching and lookup
βββ main.js # Report and action entry points + scheduling
βββ utils.js # Config helpers and contact list processing
MIT