Live, while it is happening

The engine room

Every message below is a real send, the moment it happens. The code is the real sender, and the highlight follows the checks each message passes before it is allowed to leave.

sent
still to go
per hour
addressed by name
refused before sending
finishes about
scripts/outreach/send-gmail.mjs waiting for the next send…
320let sent = 0, skipped = 0;
321/* A database blip must cost one message, not the whole night.
322 *
323 * thunga@ died at 06:07 on EADDRNOTAVAIL — a transient failure opening a
324 * connection to Supabase — with 100 messages still to send. Every Gmail call in
325 * this file already retries, because the network between here and Google was
326 * expected to wobble. The network between here and the DATABASE was not, so a
327 * single failed query threw out of the loop and took the process with it.
328 *
329 * The pilot this was written for made seven queries over 22 minutes. This run
330 * makes four per message for eight hours. At that length "it usually works" is
331 * not a property you can rely on, and the supervisor restarting the process is
332 * a coarse recovery: it loses whatever was in flight and waits up to fifteen
333 * minutes to notice. Catching here is the cheap, precise version. */
334for (const d of approved) {
335 try {
336 if (!okAddr.has(d.to_email.toLowerCase())) {
337 await sql.query(
338 `update outreach_drafts set status = 'rejected',
339 why_this_company = why_this_company || ' | SKIPPED AT SEND: address no longer in v_sendable'
340 where id = $1`, [d.id]);
341 console.log(` SKIP ${d.company} — ${d.to_email} left v_sendable since approval`);
342 skipped++;
343 continue;
344 }
345
346 if (!REALLY) {
347 console.log(` would send ${d.company.padEnd(40)} -> ${d.to_email} "${d.subject}"`);
348 continue;
349 }
350
351 /* CLAIM IT. Three senders run at once and a keeper restarts any that dies;
352 pgrep is a race, so a twin can exist for an instant. "select approved, then
353 send" is read-then-write with a gap, and in that gap two processes send the
354 same mail to the same partner. Moving approved -> sending in ONE statement
355 closes it: whoever gets the row owns it, the other gets zero rows and walks
356 on. This is the never-mail-twice rule made structural. */
357 const claim = await sql.query(
358 `update outreach_drafts set status = 'sending'
359 where id = $1 and status = 'approved' returning id`, [d.id]);
360 if (!claim.rowCount) {
361 console.log(` claimed by another sender, skipping ${d.company}`);
362 continue;
363 }
364
365 /* Belt and braces: has this exact address already had this touch, under any
366 draft row? The unique index would refuse it at the end anyway, but finding
367 out BEFORE transmitting is the difference between a blocked write and a
368 delivered duplicate. */
369 const dup = await sql.query(
370 `select 1 from outreach_drafts
371 where lower(to_email) = lower($1) and campaign = $2 and touch = $3
372 and status = 'sent' limit 1`, [d.to_email, d.campaign, d.touch]);
373 if (dup.rowCount) {
374 await sql.query(
375 `update outreach_drafts set status = 'rejected',
376 why_this_company = why_this_company || ' | ALREADY SENT to this address for this touch'
377 where id = $1`, [d.id]);
378 console.log(` ALREADY SENT to ${d.to_email} — refusing to send twice`);
379 skipped++;
380 continue;
381 }
382
383 const cc = TEAM.filter((m) => m !== SENDER);
384 const raw = mime(d.to_email, d.subject, d.body, sigHtml ? htmlBody(d.body) : null,
385 d.campaign, d.touch, cc, ATTACH, INLINE);
386 // A transient ETIMEDOUT on one call must not kill the run (it did, 10 Aug —
387 // 2 of 49 sent, process dead). Retry the network; skip the draft on
388 // persistent failure — it stays approved and the next run picks it up.
389 let res = null;
390 for (let attempt = 1; attempt <= 3; attempt++) {
391 try {
392 // 60s hard timeout: a dead-air connection hung the 10 Aug night run for
393 // 8 hours. A send that times out is treated as NOT sent; reconcile
394 // against the mailbox's real Sent folder before ever assuming otherwise.
395 res = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', {
396 method: 'POST',
397 headers: { authorization: `Bearer ${await token()}`, 'content-type': 'application/json' },
398 body: JSON.stringify({ raw: b64url(raw) }),
399 signal: AbortSignal.timeout(60000),
400 });
401 break;
402 } catch (e) {
403 console.error(` network error for ${d.company} (attempt ${attempt}/3): ${e.cause?.code || e.message}`);
404 // THE CONVEYOR LESSON (10 Aug): a send whose response never arrives may
405 // still have TRANSMITTED. Before any retry, ask the mailbox itself.
406 try {
407 const chk = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages?' +
408 new URLSearchParams({ q: `in:sent to:${d.to_email} newer_than:1d`, maxResults: '1' }),
409 { headers: { authorization: `Bearer ${await token()}` }, signal: AbortSignal.timeout(30000) });
410 if (chk.ok && ((await chk.json()).resultSizeEstimate || 0) > 0) {
411 console.error(` ${d.company}: found in Sent despite the error — marking sent, NOT retrying`);
412 res = { ok: true, alreadyInSent: true };
413 break;
414 }
415 } catch { /* check itself failed — fall through to retry/backoff */ }
416 if (attempt < 3) await new Promise((r) => setTimeout(r, 15000 * attempt));
417 }
418 }
419 /* Release the claim on a failure we KNOW did not transmit, so the next run
420 picks it up. Both paths below have already asked the mailbox's own Sent
421 folder and been told the message is not there. A claim held by a dead
422 process would otherwise park that partner permanently in 'sending'. */
423 if (!res) {
424 await sql.query(`update outreach_drafts set status = 'approved' where id = $1 and status = 'sending'`, [d.id]);
425 console.error(` GIVING UP on ${d.company} this run — released, next run retries`);
426 continue;
427 }
428 if (!res.ok) {
429 const txt = await res.text();
430 await sql.query(`update outreach_drafts set status = 'approved' where id = $1 and status = 'sending'`, [d.id]);
431 console.error(` FAILED ${d.company}: ${res.status} ${txt} — released, not marked sent`);
432 // A 429 or 403 is Google telling us to slow down or stop. Backing off here
433 // rather than hammering is what keeps the account alive.
434 if (res.status === 429 || res.status === 403) {
435 console.error(` RATE LIMITED on ${SENDER} — pausing 10 minutes`);
436 await new Promise((r) => setTimeout(r, 600000));
437 }
438 continue;
439 }
440
441 await sql.query('begin');
442 try {
443 await sql.query(`update outreach_drafts set status = 'sent', sent_at = now() where id = $1 and status = 'sending'`, [d.id]);
444 await sql.query(
445 `insert into agent_events (agent_id, kind, note, actor_email)
446 values ($1, 'email', $2, $3)`,
447 [d.agent_id, `Touch ${d.touch} sent (${d.campaign}) via Gmail pilot`, SENDER]);
448 await sql.query(
449 `insert into agent_state (agent_id, status, first_emailed_at, last_emailed_at)
450 values ($1, 'emailed', now(), now())
The six checks below run for every single message. Any one of them can stop it.
Sending nowconnecting…
  1. waiting…
Re-prove the address
The never-mail-twice list is checked again at the moment of sending, not at approval. An address that left it since is skipped and flagged.
Claim the draft
approved → sending in one statement. Three senders run at once; whoever gets the row owns it, the others walk on. This is what makes a double send impossible.
Refuse a second send
Has this exact address already had this touch? The database also enforces it with a unique index, but finding out BEFORE transmitting is the difference between a blocked write and a delivered duplicate.
Build the message
Plain text and HTML, the WorldZone signature card carried inside the message as a cid: part, and the Independence Day PDF attached.
Hand it to Gmail
A 60-second deadline, three retries — and before any retry it asks the mailbox’s own Sent folder whether the message already went, because a response that never arrives is not the same as a message that never sent.
Record it, or none of it
sent_at, the company timeline entry and the relationship state move together in one transaction, or nothing moves. The record can never claim a mail that did not go.

How 1,486 emails go out safely

Sending a thousand emails is easy. The work is in what the system refuses to do — and every refusal below is enforced in code, not in someone remembering. These are the real numbers from this campaign.

4,836
Indian freight companies on file
Harvested from JCtrans, FIATA, WCA and the registries. 132 of these existed a week ago.
1,644
have an address we can prove
3,192 have none. We do not guess one — a blank beats a wrong address, always.
1,506
also publish a phone
The phone is never dialled here. It is a quality signal: a company maintaining both is a company whose details are current.
19
refused before sending
Domains with no mail server at all, marketplace addresses, and one address that turned out to be ROT13-obfuscated and scraped literally.
1,103
actually sent
470 of them opened with a real person's name rather than "Dear Sir/Madam".
01

Nobody can be written to twice

Three programs send at once, and a supervisor restarts any that dies. That creates a window where two copies of the same sender could exist for an instant and both pick up the same company. Rather than trust that the window is small, the rule is enforced twice: the database physically refuses a second send, and each message is claimed in a single statement before it is built.

create unique index outreach_one_send_per_address
  on outreach_drafts (lower(to_email), campaign, touch)
  where status = 'sent';

In plain terms: once a message to an address has been recorded as sent, the database will reject any attempt to record a second one. Not warn — reject. We proved it by trying: it refused.

update outreach_drafts set status = 'sending'
 where id = $1 and status = 'approved'
 returning id;

And this is the claim. Whichever program updates the row first owns that message; the other gets nothing back and moves on. There is no moment where both think it is theirs.

02

Every name has to be earned

A greeting is the first thing anyone reads, so a wrong name is worse than no name. Names come from three sources, in order of how well they are proved: a contact already on file, the company's WCA profile, or the address itself — pankaj.gupta@ is Pankaj, and nobody typed that.

The third source is where a careless version does damage. The first run of it produced "Dear Privacy,", "Dear Hello," and "Dear Cochin,". So a bare word now has to be corroborated against the 1,986 real first names already in the contact record before it counts as a person:

if (parts.length === 1 && !FIRST_NAMES.has(first)) return null;

It also caught the opposite mistake: 42 contacts are stored as "Mr. Sunil Kapoor", which would have opened 42 emails with "Dear Mr.,". Stripping the title properly gained 83 usable names. 470 messages carry a name; the rest honestly say Sir/Madam.

03

The pace is arithmetic, not a guess

Google allows 2,000 recipients per account per day. Every message here carries two colleagues on copy, so one message is three recipients — which is why the limit is counted in recipients and not in emails.

Split across three mailboxes at one message a minute each, the run is roughly 500 messages per account and finishes in about eight hours, using around 1,500 of each account's 2,000 recipients. The interval is randomised by ±12.5% so it reads as a person working, not a machine firing on a metronome:

await new Promise((r) => setTimeout(
  r, GAP * 1000 * (0.875 + Math.random() * 0.25)));
04

It stops itself when it should

A supervisor checks every fifteen minutes and can halt everything. It distinguishes two failures that look identical in a summary and are not remotely the same thing.

A dead address costs one wasted message and tells Google nothing about us — every scraped list has them. A spam block is Google saying it does not trust the sender, and that damages the domain the company runs its shipments on. Holding both to one number meant an ordinary stale list could halt a healthy campaign — which is exactly what happened at 00:26, and why the thresholds are now separate:

if [ "$spct" -ge 2 ];  then  # spam blocks — stop, this is reputation
if [ "$hpct" -ge 15 ]; then  # dead addresses — stop, the list is too stale

It fired once, at 00:26, on a bounce rate of 8%. Reading the failures rather than counting them changed the answer: every one was a dead address and not a single message had been blocked as spam. The campaign was in no trouble; the threshold was wrong. That is the version running now.

05

A send that is not recorded did not happen

The moment a message leaves, three things have to be written: the message is marked sent, a dated line is added to that company's timeline, and the relationship state moves to "emailed". They move together in one transaction, or none of them move.

This is why the counter on this page can be trusted. It is not a tally the sender keeps in memory — it is the same rows that were written inside the transmission itself. The record cannot claim a message that did not go, and cannot forget one that did.

06

The failure nobody thinks about

A Google access token lives one hour. This sender was originally written for a 30-message pilot that finished in 22 minutes, so it fetched one token at startup and never thought about it again.

At one message a minute for eight hours, that token dies at message 31 — at half past midnight, with nobody awake, and every send after it failing silently until morning. It now renews itself at the 45-minute mark, before the hour is up:

if (!tokenValue || Date.now() - tokenMintedAt > 45 * 60 * 1000) {
  tokenValue = await gmailToken();   // and it re-proves the mailbox
}

And it re-proves, every time, that the token opens the mailbox it claims to. A sender that quietly writes from the wrong account is a worse outcome than one that stops.