Retry Resend calls on 429 and throttle Newsletter Archive lookups; v2.7.1
Publish Container / build (push) Successful in 15m57s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-01 09:14:49 -04:00
1 parent 66cdb9c4c9
commit dad0e5691e
4 files changed
+37 -17

No files matched your search

+10 -9
View File
@@ -33,6 +33,7 @@ import {
buildContactAdminNotificationTemplate,
sendResendEmailWithRetry,
syncContactToResend,
resendRetry,
} from '../email.js'
// RFC 5322 msg-id: "<" printable-ASCII-no-whitespace ">"
@@ -446,7 +447,7 @@ export function register(app) {
const out = []
let after
for (let page = 0; page < 50; page += 1) {
const { data, error } = await resend.contacts.list({ limit: 100, ...(segmentId ? { segmentId } : {}), ...(after ? { after } : {}) })
const { data, error } = await resendRetry(() => resend.contacts.list({ limit: 100, ...(segmentId ? { segmentId } : {}), ...(after ? { after } : {}) }))
if (error) throw new Error(error.message || 'Resend contacts.list failed')
const rows = data?.data ?? []
out.push(...rows)
@@ -550,13 +551,13 @@ export function register(app) {
try {
if (action === 'add-to-resend') {
const { firstName, lastName } = splitName(target.name)
const { error } = await resend.contacts.create({
const { error } = await resendRetry(() => resend.contacts.create({
email: target.email, firstName, lastName, unsubscribed: false,
...(segmentId ? { segments: [{ id: segmentId }] } : {}),
})
}))
if (error) throw new Error(error.message)
} else {
const { error } = await resend.contacts.segments.add({ email: target.email, segmentId })
const { error } = await resendRetry(() => resend.contacts.segments.add({ email: target.email, segmentId }))
if (error) throw new Error(error.message)
}
done += 1
@@ -583,7 +584,7 @@ export function register(app) {
const templates = []
let after
for (let page = 0; page < 5; page += 1) {
const { data, error } = await resend.templates.list({ limit: 100, ...(after ? { after } : {}) })
const { data, error } = await resendRetry(() => resend.templates.list({ limit: 100, ...(after ? { after } : {}) }))
if (error) throw new Error(error.message)
templates.push(...(data?.data ?? []))
if (!data?.has_more || !data.data?.length) break
@@ -606,7 +607,7 @@ export function register(app) {
}
try {
const resend = new Resend(process.env.RESEND_API_KEY)
const { data, error } = await resend.templates.get(req.params.id)
const { data, error } = await resendRetry(() => resend.templates.get(req.params.id))
if (error || !data) {
res.status(404).json({ message: error?.message || 'Template not found.' }); return
}
@@ -761,7 +762,7 @@ export function register(app) {
let topicId
if (tagNewEpisode === true) {
const { data: topics, error: topicsError } = await resend.topics.list()
const { data: topics, error: topicsError } = await resendRetry(() => resend.topics.list())
if (topicsError) throw new Error(topicsError.message)
topicId = (topics?.data ?? []).find(t => String(t.name || '').trim().toLowerCase() === 'new episode')?.id
if (!topicId) {
@@ -769,7 +770,7 @@ export function register(app) {
}
}
const { data, error } = await resend.broadcasts.create({
const { data, error } = await resendRetry(() => resend.broadcasts.create({
name: subject.trim().slice(0, 120),
segmentId,
from: getResendFromAddress(),
@@ -780,7 +781,7 @@ export function register(app) {
...(topicId ? { topicId } : {}),
send: true,
...(scheduled ? { scheduledAt: scheduled } : {}),
})
}))
if (error || !data?.id) throw new Error(error?.message || 'Resend did not return a broadcast id.')
console.log(`[admin-broadcast] ${scheduled ? 'scheduled' : 'sent'} Resend broadcast ${data.id}: ${subject.trim()}`)
+9 -3
View File
@@ -4,6 +4,7 @@ import { requireAdminAuth } from '../auth.js'
import { state } from '../state.js'
import { queueNewsletterIssuesWrite } from '../data.js'
import { escapeHtml } from '../helpers.js'
import { resendRetry } from '../email.js'
const BASE = 'https://versebyversewithnate.us'
const NEW_EPISODE_TOPIC_NAME = 'new episode'
@@ -117,8 +118,8 @@ export function register(app) {
try {
const resend = new Resend(process.env.RESEND_API_KEY)
const [{ data: topicsData, error: topicsError }, { data: listData, error: listError }] = await Promise.all([
resend.topics.list(),
resend.broadcasts.list(),
resendRetry(() => resend.topics.list()),
resendRetry(() => resend.broadcasts.list()),
])
if (topicsError) throw new Error(topicsError.message)
if (listError) throw new Error(listError.message)
@@ -126,7 +127,12 @@ export function register(app) {
const newEpisodeTopic = (topicsData?.data ?? []).find(t => String(t.name || '').trim().toLowerCase() === NEW_EPISODE_TOPIC_NAME)
const sentBroadcasts = (listData?.data ?? []).filter(b => b.status === 'sent')
const details = await Promise.all(sentBroadcasts.map(b => resend.broadcasts.get(b.id)))
// Fetch a few at a time so a long broadcast history can't exceed Resend's requests-per-second limit.
const details = []
for (let i = 0; i < sentBroadcasts.length; i += 4) {
const chunk = await Promise.all(sentBroadcasts.slice(i, i + 4).map(b => resendRetry(() => resend.broadcasts.get(b.id))))
details.push(...chunk)
}
const broadcasts = details.map(({ data }, i) => {
const summary = sentBroadcasts[i]