DEV Community

Huoxing Shop
Huoxing Shop

Posted on

A Simple Spreadsheet-and-Script Workflow for Tracking Many Social Media Accounts

If you manage more than a handful of social media or ad accounts — for clients, for several brands, or for testing campaigns — you've probably hit the moment where "I'll remember it" stops working. Which account has 2FA on an authenticator app vs. SMS? When was the password last rotated? Which recovery email belongs to which profile? Which account hasn't been logged into for six weeks?

You don't need a SaaS dashboard for this. A well-structured spreadsheet plus a 60-line script gets you most of the way. This post walks through the schema I'd recommend, a few rules that keep it safe, and a small Python script that turns the sheet into a daily "what needs attention" report.

1. Rule zero: the sheet is an inventory, not a vault

Before anything else: do not put passwords, 2FA seeds, or backup codes in the spreadsheet. Those belong in a password manager (Bitwarden, 1Password, KeePassXC, etc.). The sheet stores metadata — what exists, where its secrets live, and when things were last checked.

That separation matters because spreadsheets get shared, exported, emailed and synced to places you forget about. A leaked inventory is annoying; a leaked inventory with credentials is a disaster.

2. The schema

One row per account. Here are the columns I've found useful:

Column Example Why
id fb-003 Stable short ID you can reference anywhere
platform facebook Lowercase, consistent values for filtering
handle @brand_eu Public name/handle
purpose ads-eu What the account is for
owner anna Who is responsible
vault_ref Bitwarden/Clients/fb-003 Where the secrets live
twofa_method totp / sms / none Spot weak setups fast
recovery_email ops+fb003@… Must be an inbox you control
browser_profile profile-07 Which isolated profile logs into it
last_login 2026-10-28 Detect dormant accounts
last_pw_rotation 2026-08-01 Rotation reminders
status active / review / restricted Current health
notes free text Context for teammates

A few tips:

  • Use data validation (dropdowns) for platform, twofa_method and status. Free-text values like "FB", "facebook", "Facebook " will quietly break your filters.
  • Use ISO dates (YYYY-MM-DD). They sort correctly and parse without surprises.
  • Plus-addressing (ops+fb003@yourdomain) gives every account a distinct recovery address while keeping one inbox.

3. Health checks worth automating

Once the data is structured, "health" becomes a set of simple rules:

  1. No 2FA, or SMS-only 2FA → flag. Authenticator apps or hardware keys are more resistant to SIM-swap issues.
  2. No vault_ref → flag. If nobody knows where the credentials are, the account is effectively unowned.
  3. Dormant: last_login older than N days (I use 30) → flag. Some platforms treat long-idle accounts with more suspicion, and you also lose track of whether they still work.
  4. Rotation due: last_pw_rotation older than 90 days → reminder.
  5. Status not active → always surface it.
  6. Duplicate browser_profile across accounts of the same platform → flag, if your policy is one profile per account.

None of this is clever. The value is that it runs every day without anyone remembering to do it.

4. The script

Export the sheet as CSV (or pull it via the Google Sheets API later — start simple). Then:

#!/usr/bin/env python3
"""Daily health report for a social/ad account inventory CSV."""
import csv
import sys
from collections import defaultdict
from datetime import date, datetime

DORMANT_DAYS = 30
ROTATION_DAYS = 90
today = date.today()

def days_since(value):
    if not value:
        return None
    return (today - datetime.strptime(value.strip(), "%Y-%m-%d").date()).days

def check(row):
    issues = []
    if row["twofa_method"] in ("", "none"):
        issues.append("no 2FA")
    elif row["twofa_method"] == "sms":
        issues.append("SMS-only 2FA")
    if not row["vault_ref"].strip():
        issues.append("no vault reference")
    d = days_since(row["last_login"])
    if d is None or d > DORMANT_DAYS:
        issues.append(f"dormant ({d if d is not None else '?'} days)")
    r = days_since(row["last_pw_rotation"])
    if r is None or r > ROTATION_DAYS:
        issues.append("password rotation due")
    if row["status"] != "active":
        issues.append(f"status={row['status']}")
    return issues

def main(path):
    with open(path, newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))

    profiles = defaultdict(list)
    for row in rows:
        if row["browser_profile"]:
            profiles[(row["platform"], row["browser_profile"])].append(row["id"])

    flagged = 0
    for row in rows:
        issues = check(row)
        key = (row["platform"], row["browser_profile"])
        if len(profiles.get(key, [])) > 1:
            issues.append(f"shares profile with {', '.join(i for i in profiles[key] if i != row['id'])}")
        if issues:
            flagged += 1
            print(f"[{row['id']}] {row['platform']} {row['handle']} ({row['owner']}): " + "; ".join(issues))

    print(f"\n{flagged}/{len(rows)} accounts need attention on {today}.")

if __name__ == "__main__":
    main(sys.argv[1] if len(sys.argv) > 1 else "accounts.csv")
Enter fullscreen mode Exit fullscreen mode

Sample output:

[fb-003] facebook @brand_eu (anna): SMS-only 2FA; password rotation due
[tt-011] tiktok @brand_test (marc): dormant (41 days)
[x-002] x @brand_support (anna): shares profile with x-005

3/24 accounts need attention on 2026-11-02.
Enter fullscreen mode Exit fullscreen mode

5. Running it on a schedule

  • cron on any always-on machine: 0 9 * * 1-5 python3 report.py accounts.csv | mail -s "Account health" ops@yourdomain
  • GitHub Actions with a scheduled workflow, if the CSV lives in a private repo (keep it private — it's still sensitive metadata).
  • Google Apps Script, if you'd rather stay inside Sheets. A trimmed-down equivalent:
function dailyHealth() {
  const rows = SpreadsheetApp.getActive().getSheetByName('accounts')
    .getDataRange().getValues();
  const head = rows.shift();
  const col = n => head.indexOf(n);
  const now = new Date();
  const out = rows.filter(r => {
    const idle = (now - new Date(r[col('last_login')])) / 864e5;
    return r[col('twofa_method')] !== 'totp' || idle > 30 || r[col('status')] !== 'active';
  }).map(r => `${r[col('id')]} ${r[col('handle')]}`);
  if (out.length) MailApp.sendEmail(Session.getActiveUser().getEmail(),
    'Accounts needing attention', out.join('\n'));
}
Enter fullscreen mode Exit fullscreen mode

Add a time-driven trigger and you get a morning email.

6. Onboarding new accounts

The workflow is most useful at the moment an account enters your hands — whether you created it, inherited it from a client, or purchased it. A short checklist, mirrored as columns in the sheet:

  1. Log in from the browser profile you'll use going forward and record it.
  2. Change the password and store it in the vault; fill vault_ref and last_pw_rotation.
  3. Replace the recovery email/phone with ones you control.
  4. Enable authenticator-app 2FA and save backup codes in the vault.
  5. Review active sessions and connected apps; revoke anything unknown.
  6. Set status=active only once all of the above are done.

If you source ready-made accounts for ads or testing — from a provider such as Huoxing Shop or anywhere else — running every new account through this same checklist on day one keeps your inventory consistent no matter where an account came from.

Wrapping up

The whole system is: one sheet with strict columns, secrets kept elsewhere, and a tiny script that turns rules into a daily list. It scales comfortably to a few hundred accounts, and when it stops scaling you'll already have clean data to move into something bigger.


Author note: I write about practical account operations for Huoxing Shop (火星小铺), a store for social media and ad accounts across Facebook, Google, TikTok, X, Instagram and Telegram — see hxpuzi.com. Questions: support@hxpuzi.com.

Top comments (0)