A loop of 1,400 awaited sends does not fit in a serverless function's execution limit. The platform kills it partway, sees a failed invocation, and retries from the top. Everyone the first run reached gets a second mail, which matches your 600.
Two changes. First, make the send resumable: write a sent_at on the donor row immediately after each successful send and select only where sent_at is null, so a retry picks up where it stopped instead of restarting. Second, pass an idempotency key per recipient so a duplicate call is a no-op at Resend's side:
await resend.emails.send(payload, { idempotencyKey: `thanks-${campaignId}-${donorId}` })Then move the loop to a job that processes a batch per invocation. 1,400 mails is not a request, it is a queue.