Skip to main content

Newsletter Server Actions

The newsletter system uses Next.js Server Actions rather than traditional API route handlers. These actions manage email subscriptions including subscribing, unsubscribing, and retrieving statistics. Email notifications are sent for both subscribe and unsubscribe events using configurable email providers.

Overview

ActionAuthDescription
subscribeToNewsletterPublicSubscribe an email to the newsletter
unsubscribeFromNewsletterPublicUnsubscribe an email from the newsletter
getNewsletterStatisticsNoneGet subscription statistics

These are Server Actions defined with 'use server' and invoked from React components via form submissions or direct calls, not via HTTP endpoints.

Server Actions

Subscribe to Newsletter

subscribeToNewsletter(data: { email: string })

Subscribes an email address to the newsletter. Validates the email using Zod, checks for duplicate active subscriptions, creates the database record, and sends a welcome email. The email is automatically normalized to lowercase and trimmed.

Input Validation (Zod):

FieldTypeRequiredConstraints
emailstringYesMust be a valid email format

Success Response:

{
"success": true
}

Error Responses:

{
"error": "Email is already subscribed to the newsletter",
"email": "user@example.com"
}
ErrorCondition
"Please enter a valid email address"Invalid email format (Zod validation)
"Email is already subscribed to the newsletter"Active subscription already exists
"Failed to create subscription. Please try again."Database insert failed
"Failed to subscribe to newsletter. Please try again."Unexpected error

Processing Steps:

  1. Validate and normalize email (lowercase, trim)
  2. Check for existing active subscription via getNewsletterSubscriptionByEmail
  3. Create subscription record with source "footer" via createNewsletterSubscription
  4. Send welcome email using the configured email provider (Resend or Novu)

Email send failures are caught silently and do not prevent the subscription from succeeding.

Source: template/app/[locale]/newsletter/actions.ts

Unsubscribe from Newsletter

unsubscribeFromNewsletter(data: { email: string })

Unsubscribes an email from the newsletter by setting isActive to false. Sends an unsubscribe confirmation email.

Success Response:

{
"success": true
}

Error Responses:

ErrorCondition
"Email is not subscribed to the newsletter"No active subscription found
"Failed to unsubscribe. Please try again."Database update failed

Source: template/app/[locale]/newsletter/actions.ts

Get Newsletter Statistics

getNewsletterStatistics()

Returns aggregate newsletter statistics. No input parameters required.

Success Response:

{
"success": true,
"data": {
"totalActive": 1250,
"recentSubscriptions": 45
}
}
FieldTypeDescription
totalActiveintegerNumber of currently active subscriptions
recentSubscriptionsintegerSubscriptions created in the last 30 days

Returns zeros for both fields if the query fails, ensuring graceful degradation.

Source: template/app/[locale]/newsletter/actions.ts

Database Queries

The newsletter subscription data is managed through dedicated query functions in lib/db/queries/newsletter.queries.ts.

Subscription Operations

FunctionDescription
createNewsletterSubscription(email, source)Creates a new subscription record
getNewsletterSubscriptionByEmail(email)Looks up a subscription by email
updateNewsletterSubscription(email, updates)Updates subscription fields
unsubscribeFromNewsletter(email)Sets isActive: false and records unsubscribedAt
resubscribeToNewsletter(email)Sets isActive: true and clears unsubscribedAt
getNewsletterStats()Returns active count and 30-day subscription count

All email lookups normalize the input to lowercase and trim whitespace before querying.

Source: template/lib/db/queries/newsletter.queries.ts

Configuration

Newsletter configuration constants are defined in lib/newsletter/config.ts:

NEWSLETTER_CONFIG.DEFAULT_PROVIDER = "resend"
NEWSLETTER_CONFIG.DEFAULT_FROM = "onboarding@resend.dev"
NEWSLETTER_CONFIG.DEFAULT_COMPANY_NAME = "Ever Works"

Subscription Sources

SourceDescription
footerSubscription from the site footer form
popupSubscription from a popup dialog
signupSubscription during user registration

Validation Schemas

Two Zod schemas are exported for validation:

  • emailSchema -- validates and normalizes a single email field
  • newsletterSubscriptionSchema -- validates email and source (defaults to "footer")

Email Providers

The system supports two email providers configured via .works/works.yml and environment variables:

ProviderEnvironment VariableDescription
ResendRESEND_API_KEYDefault email provider
NovuNOVU_API_KEYAlternative provider with template support

The provider is selected based on the mail.provider field in .works/works.yml. Email configuration is built dynamically from the app config using createEmailConfig().

Source: template/lib/newsletter/config.ts

Key Implementation Details

  • Server Actions: These are not REST API endpoints. They use the validatedAction wrapper from lib/auth/middleware which provides Zod schema validation before the action executes.
  • Email Normalization: All emails are normalized to lowercase and trimmed at both the action level and the database query level for consistent lookups.
  • Graceful Email Failures: Welcome and unsubscribe confirmation emails are sent via sendEmailSafely(), which catches errors silently. A failed email does not prevent the subscription operation from completing.
  • Duplicate Prevention: Before creating a subscription, the system checks for an existing active subscription using validateExistingSubscription().
  • Soft Unsubscribe: Unsubscribing sets isActive: false rather than deleting the record, preserving the subscription history.