ShipNext

Newsletter

Manage marketing email subscription status with Resend Contacts and a Dashboard settings switch.

ShipNext newsletter support manages marketing subscription state. It is separate from transactional email:

  • Transactional email: verification, reset password, magic links, in src/modules/email/.
  • Newsletter: marketing subscribe/unsubscribe, in src/modules/newsletter/.

The current implementation uses Resend Contacts and does not store newsletter rows in the local database.

Configuration

config/website.ts:

const newsletterConfig = {
  provider: "resend",
};

src/modules/newsletter/index.ts chooses the provider from websiteConfig.newsletter.provider. Currently only resend is supported.

Environment variables

Newsletter and transactional email share the Resend API key:

RESEND_API_KEY=''

If it is missing, the first subscription query or update throws RESEND_API_KEY environment variable is not set.

HTTP API

Route:

src/app/api/newsletter/route.ts

GET /api/newsletter

Returns the current logged-in user's subscription status:

{
  "email": "[email protected]",
  "subscribed": true
}

PATCH /api/newsletter

Updates the current logged-in user's status:

{
  "subscribed": false
}

The provider first looks up the Contact. If it exists, it updates unsubscribed; otherwise it creates a Contact.

Dashboard usage

The settings UI uses NewsletterSettings:

src/shared/components/dashboard/settings/newsletter-settings.tsx

When the user toggles the switch, the component calls PATCH /api/newsletter. On success it shows feedback; on failure it rolls back the UI state.

Current boundaries

  • This only tracks subscribe/unsubscribe status.
  • It does not include a campaign editor, analytics, or multiple lists.
  • GET and PATCH require a logged-in user.
  • If a user changes email address, decide whether to sync or archive the old Contact.

Checklist

  • .env.local has RESEND_API_KEY.
  • Logged-in users can see their current email in Dashboard newsletter settings.
  • Toggling the switch updates Resend Contacts.
  • Unauthenticated requests return an auth error instead of a generic 500.

Related docs:

On this page