Newsletter Library
The newsletter library provides configuration, validation, and utility functions for managing email newsletter subscriptions. It builds on top of the Mail Factory for email delivery.
File Structure
lib/newsletter/
config.ts # Configuration, types, Zod schemas, email config creation
utils.ts # Email sending, subscription validation, logging, templates
Configuration (config.ts)
Newsletter Constants
The NEWSLETTER_CONFIG object centralizes all newsletter-related constants:
export const NEWSLETTER_CONFIG = {
DEFAULT_PROVIDER: "resend",
DEFAULT_FROM: "onboarding@resend.dev",
DEFAULT_COMPANY_NAME: "Ever Works",
SOURCES: {
FOOTER: "footer",
POPUP: "popup",
SIGNUP: "signup",
} as const,
ERRORS: {
INVALID_EMAIL: "Please enter a valid email address",
ALREADY_SUBSCRIBED: "Email is already subscribed to the newsletter",
NOT_SUBSCRIBED: "Email is not subscribed to the newsletter",
SUBSCRIPTION_FAILED: "Failed to create subscription. Please try again.",
UNSUBSCRIPTION_FAILED: "Failed to unsubscribe. Please try again.",
EMAIL_SEND_FAILED: "Failed to send email. Please try again.",
STATS_FAILED: "Failed to get newsletter statistics",
} as const,
SUCCESS: {
SUBSCRIBED: "Successfully subscribed to newsletter",
UNSUBSCRIBED: "Successfully unsubscribed from newsletter",
} as const,
} as const;
Types
// Source of the subscription action
type NewsletterSource = "footer" | "popup" | "signup";
// Email provider configuration
interface EmailConfig {
provider: string;
defaultFrom: string;
domain: string;
apiKeys: { resend: string; novu: string };
novu?: { templateId?: string; backendUrl?: string };
}
// Action result for subscribe/unsubscribe operations
interface NewsletterActionResult {
success?: boolean;
error?: string;
email?: string;
}
// Newsletter statistics
interface NewsletterStats {
totalActive: number;
recentSubscriptions: number;
}
Validation Schemas
Two Zod schemas handle email validation for newsletter operations:
import { z } from "zod";
// Basic email validation
export const emailSchema = z.object({
email: z
.string()
.email(NEWSLETTER_CONFIG.ERRORS.INVALID_EMAIL)
.transform((email) => email.toLowerCase().trim()),
});
// Full subscription schema with source tracking
export const newsletterSubscriptionSchema = z.object({
email: z
.string()
.email(NEWSLETTER_CONFIG.ERRORS.INVALID_EMAIL)
.transform((email) => email.toLowerCase().trim()),
source: z
.enum(["footer", "popup", "signup"])
.default("footer"),
});
Config Creation
import { createEmailConfig, getCompanyName } from '@/lib/newsletter/config';
// Build email config from app settings and environment
const config = await createEmailConfig();
// => { provider: "resend", defaultFrom: "...", domain: "...", apiKeys: {...} }
// Get company name with fallback
const name = await getCompanyName();
// => "Ever Works" or value from content config
Utilities (utils.ts)
Sending Emails Safely
The sendEmailSafely function wraps email sending with comprehensive error handling:
import { sendEmailSafely, createEmailService } from '@/lib/newsletter/utils';
const { service, config } = await createEmailService();
const result = await sendEmailSafely(
service,
config,
{ subject: "Welcome!", html: "<p>Hi</p>", text: "Hi" },
"user@example.com",
"welcome"
);
if (result.success) {
// Email sent
} else {
console.log(result.error);
}
Subscription Validation
Check whether an email can be subscribed or unsubscribed:
import { canSubscribe, canUnsubscribe } from '@/lib/newsletter/utils';
// Check if email is not already active
const subCheck = await canSubscribe("user@example.com");
if (!subCheck.canSubscribe) {
console.log(subCheck.error); // "Email is already subscribed..."
}
// Check if email is currently active
const unsubCheck = await canUnsubscribe("user@example.com");
if (!unsubCheck.canUnsubscribe) {
console.log(unsubCheck.error); // "Email is not subscribed..."
}
The underlying validateSubscriptionStatus function queries the database:
const validation = await validateSubscriptionStatus(email, shouldBeActive);
// => { isValid: boolean, error?: string, subscription?: any }
Logging and Monitoring
import { logNewsletterActivity, trackNewsletterMetric } from '@/lib/newsletter/utils';
// Log activity for monitoring
logNewsletterActivity("subscribe", "user@example.com", "footer");
// Output: Newsletter Activity: { timestamp, action, email, source }
// Track metrics (wraps logNewsletterActivity)
trackNewsletterMetric("subscription", "user@example.com", "popup");
Template Utilities
import { getTemplateWithCompany } from '@/lib/newsletter/utils';
// Automatically injects the company name into a template function
const template = await getTemplateWithCompany(
(email, companyName) => ({
subject: `Welcome to ${companyName}`,
html: `<p>Thanks for subscribing, ${email}!</p>`,
text: `Thanks for subscribing, ${email}!`,
}),
"user@example.com"
);
Standardized Responses
import { createErrorResponse, createSuccessResponse } from '@/lib/newsletter/utils';
const error = createErrorResponse("Invalid email", "bad@", "subscribe");
// => { error: "Invalid email", email: "bad@", context: "subscribe" }
const success = createSuccessResponse("user@example.com", "subscribe");
// => { success: true, email: "user@example.com", context: "subscribe" }
Integration Flow
A typical newsletter subscription flow:
- Validate input using
newsletterSubscriptionSchema - Check eligibility using
canSubscribe - Create database record via the newsletter repository
- Send welcome email using
sendEmailSafely - Log activity using
logNewsletterActivity - Return result using
createSuccessResponseorcreateErrorResponse
// Simplified server action example
async function subscribeToNewsletter(formData: FormData) {
const parsed = newsletterSubscriptionSchema.safeParse({
email: formData.get('email'),
source: formData.get('source'),
});
if (!parsed.success) {
return createErrorResponse(NEWSLETTER_CONFIG.ERRORS.INVALID_EMAIL);
}
const { email, source } = parsed.data;
const eligibility = await canSubscribe(email);
if (!eligibility.canSubscribe) {
return createErrorResponse(eligibility.error || "Cannot subscribe");
}
// Create subscription in database...
// Send welcome email...
logNewsletterActivity("subscribe", email, source);
return createSuccessResponse(email, "subscribe");
}
Related Files
lib/newsletter/config.ts- Configuration, types, and validation schemaslib/newsletter/utils.ts- Email, validation, logging, and template utilitieslib/mail/- Email provider factory and servicelib/mail/templates/newsletter-*.ts- Newsletter email templateslib/db/queries.ts- Database queries for subscription records