Long Earth SocietyLong Earth Society

Misc · Build log · Jul 2026–now

The watchlist.

Nellis Auction sells Amazon returns out of warehouses in Mesa and Phoenix: thousands of lots, every one starting at $0, every one on a 24-hour clock. I wanted to stop scrolling it. So I wrote standing orders: type what you want once, and a bot watches both warehouses and emails you what matched, with a call on whether it's worth a bid.

01Site searchevery open lotMesa + Phoenix02Your watches50 saved searchesand · phrase · not03Detail checkmatches onlypaced · cached04Resale callFLIP/MAYBE/PASScost vs eBay net05Digest8a · 3p · 7pdeduped per inboxTHOUSANDS OF LOTS INA HANDFUL OF PICKS OUT
One sweep, start to finish. The expensive steps only run on lots that already matched a watch.
Running since
Jul 2, 2026
Standing watches
50
Lots matched
3,616
Delivered in digests
10,829

Why

Nellis is a liquidation auction. Returns and overstock come in by the truckload, get a photo, a condition grade and an "estimated retail" number, and go up at $0. Bids step up a dollar at a time and every lot closes 24 hours after it opens. Pickup only.

The interesting lots close with no bids at all. A sealed $100 item can go for $1 because nobody local happened to be looking. The business is being the one who was looking, for exactly the things you know how to resell, without spending the day refreshing a website.

It also turned out to be the cheapest parts store in the Valley for the engine work. Half the watchlist is Stirling hardware: bearings, sprag clutches, brushless outrunners, tri-clamp fittings, linear bearings, helium tanks.

How it finds things

The bot runs the same kind of search you would on the website, asking only for lots that are still open and match the watch, across both warehouses.

Site search matches loosely; a watch should not. Each term is parsed into required words, exact phrases and exclusions, and every title is re-checked locally so gear doesn't match headgear, and the designer Coach doesn't pull in a baseball coach's whistle.

Writing a watchwatch syntax
baitcasting reel -spincast          # every word must match, -word excludes
"Floor Length Mirror"               # exact phrase
tri clamp -gasket -valve            # Stirling parts, not plumbing kits
gucci >150                          # per-watch retail floor
hub motor @[email protected]       # route this watch to its own inbox

Search results don't carry everything. The live bid and the retail estimate need a closer look at each lot, which is the slow part, so the bot only does it for lots that already matched a watch, soonest-closing first, keeps it gentle, and caches what it learns. Thousands of open lots cost a handful of searches; only the few dozen that match get a closer look.

Is it worth a bid?

Nellis's "estimated retail" is optimistic. The bot discounts it by condition (what returns actually fetch: 50% sealed, 38% new, 28% opened), adds the real cost of winning (15% buyer's premium plus sales tax), and subtracts everything it costs to resell: eBay's 13.25% + $0.40, shipping by weight, and $2 of box and tape. What's left is a net, and the net makes the call.

resale_call() — simplifiedpython
def resale_call(h):
    if damaged or missing_parts or not_functional:
        return "PASS"
    tier   = "sealed" if in_package else ("new" if "new" in cond else "open")
    value  = retail * RECOVERY[tier]          # 50% / 38% / 28% of Nellis retail
    landed = bid * 1.15 * (1 + TAX)           # 15% buyer's premium + sales tax
    net    = value * (1 - 0.1325) - 0.40 - ship(weight) - 2.00 - landed
    roi    = net / landed
    if net >= 20 and roi >= 1.0: return "FLIP"
    if net >= 12:                return "MAYBE"
    return "PASS"
FLIPMAYBEPASS-20-100+10+20+30+40$0$5$10$15$20$25$30WINNING BID · $100 EST. RETAIL · 1–5 LBSEALED · $32NEW · $22OPEN · $13
Net profit as the winning bid climbs, for a $100 item. A sealed item stays a FLIP to about $10. A new-but-opened one is a FLIP only at $1 and gone by $8. This chart is the whole strategy: the wins are the lots nobody else bid on.
0510150–10% of retail: 14 lots140%10–20% of retail: 12 lots1210%20–30% of retail: 7 lots720%30–40% of retail: 15 lots1530%40–50% of retail: 11 lots1140%50%60–70% of retail: 2 lots260%70%80–90% of retail: 1 lots180%90%CURRENT BID AS % OF EST. RETAIL · 62 LOTS ON THE BOARD
Where today's 62 matches sit. Median bid is 28% of estimated retail, with 26 lots still under 20%. The bid at 8 AM isn't the price at the bell; most of these will move before they close.

Three emails a day, never the same lot twice

12a6a12p6pBoard refresh sweep (every 6 h)Board refresh sweep (every 6 h)Board refresh sweep (every 6 h)Board refresh sweep (every 6 h)Email digest · 8 AM8 AMEmail digest · 3 PM3 PMEmail digest · 7 PM7 PM3DIGESTS / DAY
Quiet sweeps keep the board fresh every six hours. Only three sweeps a day send email.

Emailing on every find would bury the inbox in a day. So the board refreshes in the background and email goes out on a fixed schedule: 8 AM, 3 PM and 7 PM, Arizona time, which never shifts for daylight saving.

Every lot-and-inbox pair is remembered. A lot that matched this morning won't show up again tonight, no matter how many watches it matches. Inboxes that would get identical digests share one send.

The schedulerpython
EMAIL_HOURS = [8, 15, 19]                     # Arizona time, no DST

def next_digest(now):
    lt = time.localtime(now)
    times = [time.mktime((lt.tm_year, lt.tm_mon, lt.tm_mday + d, h, 0, 0, 0, 0, -1))
             for d in (0, 1) for h in EMAIL_HOURS]
    return min(t for t in times if t > now)

while True:
    nxt = next_digest(time.time())
    while time.time() < nxt:                  # quiet refreshes keep the board fresh
        time.sleep(min(nxt - time.time(), 6 * 3600))
        if nxt - time.time() > 60:
            scan(send_email=False)
    scan(send_email=True)                     # the one sweep that emails
Dedupe and batchingpython
for h in matches:
    for addr in recipients_for(h["term"]):
        key = f"{h['id']}::{addr}"            # one lot, one inbox, once ever
        if key in already_emailed:
            continue
        addr_hits[addr].append(h)

# inboxes receiving the identical set of lots share one send
groups = {}
for addr, hits in addr_hits.items():
    sig = tuple(sorted(h["id"] for h in hits))
    groups.setdefault(sig, {"to": [], "hits": hits})["to"].append(addr)

The digest itself is built for a phone in a parking lot: grouped by warehouse so a pickup run reads top to bottom, each card showing photo, bid, retail, percent off, a closing countdown, a green FLIP badge with the estimated net, and a one-tap eBay sold-listings search to check the comp before bidding.

The boring parts that make it reliable

  • Atomic files. Every JSON store is written to a temp file and swapped in with os.replace, with a one-generation backup of the watches. A crash mid-write never leaves a corrupt watchlist.
  • Data next to the script. Paths resolve from the script's own folder, so a shortcut, a new terminal or the startup folder all find the same watches.
  • A small control panel. A simple board to add a watch, route it to an inbox, dismiss a lot or send a test email. A new watch searches for just that term right away, in the background.
  • Second chances. Lots whose page fetch failed get one retry before the digest goes out, so emails don't ship with blank prices. Failures are never cached.
  • A watchdog. If the bot ever stops, something outside it notices and starts it again.
Never a half-written filepython
def save_watches(ws):
    shutil.copy2(PATH, PATH + ".bak")         # one-generation backup
    with open(PATH + ".tmp", "w") as f:
        json.dump(ws, f, indent=2)
    os.replace(PATH + ".tmp", PATH)           # atomic: never a half-written file

What I learned

  • Search is cheap; detail is expensive. Filter as much as possible on the index before touching a single product page.
  • The edge is attention, not price. The winning lots aren't mispriced; nobody saw them. A bot that sees everything is the whole advantage.
  • Discount the sticker. Liquidation "retail" is a ceiling. A model that starts from 28–50% of it is the only one that matched what things actually sold for.
  • Email is a product. Schedule, dedupe, group and badge it, or it gets ignored by the third day.

Figures from the live bot's files on Oct 18, 2026. Resale constants are the ones in the code; tune them to your own fees and shipping. A sibling project, a full-catalog scanner, is a story for another post. Questions: Discord.