Ask
96
@resole_ruth ·

resend threw 429s halfway through a 2,400 person waitlist blast Waitlist

Launch email went out this morning. I had 2,400 confirmed addresses in a subscribers table and a script that basically did await Promise.all(rows.map(r => resend.emails.send(...))). Roughly the first 600 went out, then everything after that came back rate_limit_exceeded and the script exited.

So now some people have the launch email, some do not, and I cannot tell who is who because I only logged failures, not successes. Re-running would double-send to the first 600. What is the correct shape for this so I can finish the send tonight without spamming anyone twice?

9 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @gradschool_gary · 3h ago · 2 replies

    Separate from the rate limit — 2,400 emails in one burst from a domain that has probably sent a few hundred in its life is exactly the pattern that gets a sending domain throttled. Even if the API had accepted all of them, a solid chunk would have landed in Promotions or nowhere at all.

    Spread it. 300 an hour across the day. Nobody on a waitlist cares whether your email arrives at 9am or 3pm, and your domain reputation cares enormously.

    44
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @harlan_voss · 3h ago

      This. I ramp anything over 500 across two days now. Same copy, same list quality, and open rate went from 31% on the burst send to 48% on the ramped one.

      15
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @headless_hugo · 3h ago · 3 replies

    Promise.all over 2,400 sends is 2,400 concurrent requests, so you did not hit the limit halfway, you hit it immediately and 600 happened to squeak through before the bucket emptied. The default is around 2 requests a second on most plans.

    Two changes. Use the batch endpoint instead of one call per recipient — it takes up to 100 messages per request, so 2,400 people is 24 calls, not 2,400. And write the send state before you send, not after:

    UPDATE subscribers SET state='claimed', batch_id=$1
    WHERE id = ANY($2) AND state='pending'
    RETURNING id
    

    Only send the rows that come back from the RETURNING. Now the process can die anywhere and a re-run picks up exactly what is left, and nobody gets two copies.

    71
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @resole_ruth · 3h ago

      The claim-then-send ordering is the bit I was missing. I had it the other way round, which is exactly why my log is useless. Rewrote it with chunks of 100 and a claim step, finished the remaining 1,800 in about a minute.

      26
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
    • @two_calendars · 3h ago

      Add a provider_message_id column while you are in there. When someone emails asking why they got nothing you can answer in ten seconds instead of digging through a dashboard search box.

      18
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @hemline_hank · 3h ago · 2 replies

    Put an await sleep(100) between sends and you are at 10 a second, comfortably under any provider limit. Sequential loop, no concurrency library, done in four minutes.

    18
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
    • @headless_hugo · 3h ago

      10 a second is five times over the default, so that loop 429s too, just more politely. If you insist on sequential the sleep has to be 500ms and you are looking at 20 minutes for the list. The batch endpoint is strictly better and it is one line of difference.

      16
      Share
      Reply

      Answering anonymously — a moderator will review it first.

      Report
  • @vesting_cliff · 3h ago

    For tonight: query everyone with no successful send row, chunk it, send those. Then for the next launch stop hand-rolling it and use a broadcast or audience feature — you get suppression lists and unsubscribe handling for free, and you stop owning the boring half of the problem.

    22
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @slowtrainjo · 3h ago

    Every launch script I have ever written eventually needed a resume flag. Assume the process dies at 40% and design for it on line one. Same lesson as any batch job, it just hurts more when the batch is people.

    12
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report