PayloadSolutions

API

payload.resendBroadcasts for server code, and the REST endpoints the admin uses.

payload.resendBroadcasts

Attached in onInit, before your own onInit runs, and typed through module augmentation — no import needed. getResendBroadcasts(payload) returns the same object and throws a clear error when the plugin is missing.

MethodWhat it does
send(campaignId, { scheduledAt? })sends a draft or failed campaign to its list now, or at scheduledAt (a Date or ISO string, at least a minute ahead). Returns the updated campaign
cancel(campaignId)cancels a scheduled send, deletes the broadcast in Resend, returns the campaign as a draft
refreshStatus(campaignId)asks Resend where a scheduled or sending broadcast is, and records it
render({ campaign, mode?, subscriber? })renders a campaign (a document or an id). mode: 'preview' (default) fills every variable from subscriber; mode: 'broadcast' produces exactly what is sent to Resend
resync({ lists?, force? })reconciles Resend with the sources; limits it to some lists, or updates every contact with force
syncDocument({ collection, doc })pushes one subscriber document now: contact plus segment membership
idle()resolves once every background sync this process started has finished — useful in tests and scripts
clientthe throttled Resend client: client.call(label, (resend) => resend.contacts.get(…))
optionsthe sanitized plugin options

Errors from Resend are thrown as ResendApiError, with Resend's error name in code and the HTTP status in statusCode. isNotFound(error) checks for a 404.

import { ResendApiError } from '@payload-solutions/plugin-resend-broadcasts'

try {
  await payload.resendBroadcasts.send(id)
} catch (error) {
  if (error instanceof ResendApiError && error.code === 'validation_error') {
    // e.g. the from address is not on a verified domain
  }
  throw error
}

Rendering without sending

const { html, text, subject } = await payload.resendBroadcasts.render({
  campaign: id,
  mode: 'broadcast',
})

render with a subscriber takes the plugin's collection-independent shape. Build one from a document with toSubscriber:

import { toSubscriber } from '@payload-solutions/plugin-resend-broadcasts'

const source = payload.resendBroadcasts.options.sources.find((s) => s.collection === 'subscribers')!
const doc = await payload.findByID({ collection: 'subscribers', id })
const preview = await payload.resendBroadcasts.render({ campaign, subscriber: toSubscriber(source, doc) })

REST endpoints

All of them except the webhook need a logged-in user who passes the plugin's access functions. Campaign and list paths use your slugs — the defaults are shown.

Method and pathAccessWhat it does
POST /api/newsletter-campaigns/:id/previewmanagebody { subscriber?: { collection, id } } → html, text, subject, preheader, the subscriber, the list, the campaign status, and every variable with this subscriber's value
POST /api/newsletter-campaigns/:id/testmanagebody { to, subscriber? } → sends a test through Resend's email API
POST /api/newsletter-campaigns/:id/sendsendbody { scheduledAt? } → sends or schedules the broadcast
POST /api/newsletter-campaigns/:id/cancelsendcancels a scheduled send
POST /api/newsletter-campaigns/:id/refreshmanagerefreshes the status from Resend
GET /api/newsletter-lists/:id/subscribersmanage?search=&page=&limit=&subscribed=true → the list's members, read with the caller's own access
POST /api/newsletter-lists/:id/resyncsendstarts a background resync of one list → { runId }
POST /api/resend-broadcasts/resyncsendbody { force? } → starts a full background resync → { runId }
POST /api/resend-broadcasts/webhooksignatureResend's contact events; see Webhooks

A background run is a document in newsletter-sync-runs; poll GET /api/newsletter-sync-runs/:runId until status is finished or failed. It carries stats and errors.

The preview response also carries privateImages: image URLs a reader could not load, which block sending.

Errors come back as { message } with a meaningful status: 400 for something to fix, 403 without access, 409 when the campaign is in the wrong state, 502 when Resend refused the call (with its code).

Exports

ExportWhat it is
resendBroadcastsPluginthe plugin
getResendBroadcastspayload.resendBroadcasts, or a clear error
DefaultTemplate, defaultStyles, darkModeCSS, EMAIL_CLASSbuilding blocks for templates
createEmailEditor, emailEditorFeatures, ButtonBlock, createImageBlockthe campaign body editor, to extend; pass { mediaCollection } to include the Image block
findPrivateImages, isPublicImageUrl, absoluteUrl, IMAGE_WIDTHSthe image checks and URL handling the send path uses
markdownToLexicalMarkdown (with <Button label="…" url="…" />) → the body editor's Lexical state, for seeding campaigns or the footer from code
renderCampaign, createResolver, variableCatalog, lexicalToReact, interpolate, extractTokensthe rendering pipeline
toSubscribera source document → the subscriber shape
createResendClient, ResendApiError, isNotFoundthe throttled client
RESEND_UNSUBSCRIBE_PLACEHOLDER, DEFAULT_SLUGS, ENDPOINT_BASE, LOCKED_STATUSESconstants
typesResendBroadcastsPluginOptions, SourceConfig, ContactPropertyDefinition, EmailTemplate, TemplateStyles, Subscriber, CampaignDoc, ListDoc, RenderedCampaign, ResyncResult, …

On this page