{
  "markdown": "# Payload Newsletter Plugin\n\n[![npm version](https://img.shields.io/npm/v/payload-plugin-newsletter.svg?cache=300)](https://www.npmjs.com/package/payload-plugin-newsletter)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nA complete newsletter management plugin for [Payload CMS](https://github.com/payloadcms/payload) that provides subscriber management, magic link authentication, and email service integration out of the box.\n\n> **Important**: Version 0.8.7+ includes critical fixes for Payload v3 compatibility. If you're using Payload v3, please ensure you're on at least version 0.8.7 of this plugin.\n\n## Features\n\n- 📧 **Complete Subscriber Management** - Ready-to-use subscriber collection with all essential fields\n- 🔐 **Magic Link Authentication** - Passwordless authentication for subscribers (separate from Payload auth)\n- 📨 **Email Service Integration** - Built-in support for Resend and Broadcast\n- 📅 **Newsletter Scheduling** - Schedule newsletters from your articles collection\n- ⚛️ **React Components** - Pre-built signup forms and preference management UI\n- 🌍 **Internationalization** - Multi-language support built-in\n- 📊 **Analytics Ready** - UTM tracking and signup metadata collection\n- ⚙️ **Admin UI Configuration** - Manage email settings through Payload admin panel\n- 🔄 **Real-time Webhook Sync** - Receive subscriber and broadcast events from email services via webhooks\n- 👁️ **Email Preview** - Real-time preview with desktop/mobile views (v0.9.0+)\n- ✅ **Email Validation** - Built-in validation for email client compatibility (v0.9.0+)\n- 📝 **Email-Safe Editor** - Rich text editor limited to email-compatible features (v0.9.0+)\n- 📬 **Broadcast Management** - Create and send email campaigns with provider sync (v0.10.0+)\n- 🎨 **React Email Templates** - Customizable email templates with React Email (v0.12.0+)\n\n## Prerequisites\n\n- Payload CMS v3.0.0 or higher\n- A Media collection configured in your Payload project (required for image support in broadcasts)\n\n## Quick Start\n\n### 1. Install the plugin\n\n```bash\nbun add payload-plugin-newsletter\n# or\nnpm install payload-plugin-newsletter\n# or\nyarn add payload-plugin-newsletter\n# or\npnpm add payload-plugin-newsletter\n```\n\n### 2. Add to your Payload config\n\n```typescript\nimport { buildConfig } from 'payload/config'\nimport { newsletterPlugin } from 'payload-plugin-newsletter'\n\nexport default buildConfig({\n  plugins: [\n    newsletterPlugin({\n      // Choose your email provider\n      providers: {\n        default: 'resend', // or 'broadcast'\n        resend: {\n          apiKey: process.env.RESEND_API_KEY,\n          fromAddress: 'hello@yoursite.com',\n          fromName: 'Your Newsletter',\n          audienceIds: {\n            en: {\n              production: 'your_audience_id',\n              development: 'your_dev_audience_id',\n            },\n          },\n        },\n      },\n    }),\n  ],\n  // ... rest of your config\n})\n```\n\n### 3. That's it! 🎉\n\nThe plugin automatically adds:\n- A `subscribers` collection to manage your subscribers\n- A `newsletter-settings` collection for email configurations (supports multiple environments)\n- API endpoints for subscription and authentication\n- Newsletter scheduling fields to your articles (optional)\n\n## Basic Usage\n\n### Frontend Integration\n\n#### Simple Newsletter Signup Form\n\n```tsx\nimport { NewsletterForm } from 'payload-plugin-newsletter/components'\n\nexport function MyHomepage() {\n  return (\n    <NewsletterForm \n      onSuccess={() => console.log('Subscribed!')}\n      onError={(error) => console.error(error)}\n    />\n  )\n}\n```\n\n#### Custom Signup Form\n\n```tsx\nasync function handleSubscribe(email: string) {\n  const response = await fetch('/api/newsletter/subscribe', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ email }),\n  })\n  \n  if (!response.ok) {\n    throw new Error('Subscription failed')\n  }\n  \n  return response.json()\n}\n```\n\n### Managing Subscribers\n\nSubscribers can be managed through the Payload admin panel at `/admin/collections/subscribers`.\n\n### Email Settings\n\nAfter setup, configure email settings at `/admin/collections/newsletter-settings` in your admin panel. You can:\n- Create multiple configurations (e.g., for different environments or purposes)\n- Set one configuration as active at a time\n- Switch between email providers\n- Update API keys and settings\n- Customize email templates\n- Set subscription preferences\n\n**Note**: Only one configuration can be active at a time. The plugin will use the active configuration for sending emails.\n\n## Initial Setup\n\nAfter installing the plugin, you'll need to:\n\n1. **Create an email configuration**:\n   - Go to `/admin/collections/newsletter-settings`\n   - Click \"Create New\"\n   - Give it a name (e.g., \"Production\" or \"Development\")\n   - Configure your email provider settings\n   - Set it as \"Active\"\n   - Save\n\n2. **Start collecting subscribers**:\n   - Subscribers will appear in `/admin/collections/subscribers`\n   - Use the provided React components or API endpoints\n\n## Email Preview Features (v0.9.0+)\n\nThe plugin includes comprehensive email preview functionality to ensure your newsletters look great across all email clients.\n\n### Email-Safe Rich Text Editor\n\nThe plugin provides a pre-configured Lexical editor with only email-compatible features:\n\n```typescript\nimport { createEmailContentField } from 'payload-plugin-newsletter/fields'\n\nconst BroadcastsCollection = {\n  fields: [\n    createEmailContentField({\n      name: 'content',\n      required: true,\n    })\n  ]\n}\n```\n\nFeatures included:\n- Basic text formatting (bold, italic, underline, strikethrough)\n- Simple links\n- Ordered and unordered lists\n- Headings (H1, H2, H3)\n- Text alignment\n- Blockquotes\n\n### Real-Time Email Preview\n\nThe plugin includes a preview component that shows how your email will look:\n\n```typescript\n{\n  name: 'preview',\n  type: 'ui',\n  admin: {\n    components: {\n      Field: 'payload-plugin-newsletter/components/EmailPreviewField'\n    }\n  }\n}\n```\n\nPreview features:\n- **Desktop & Mobile Views** - Switch between viewport sizes\n- **Live Updates** - See changes as you type\n- **Validation Warnings** - Catch compatibility issues before sending\n- **Test Email** - Send a test to your inbox\n\n### Email HTML Validation\n\nBuilt-in validation checks for:\n- HTML size limits (Gmail's 102KB limit)\n- Unsupported CSS properties\n- Missing alt text on images\n- External resources that won't load\n- JavaScript that will be stripped\n\n## Broadcast Management (v0.10.0+)\n\nCreate and send email campaigns directly from Payload:\n\n### Enable Broadcasts\n\n```typescript\nnewsletterPlugin({\n  features: {\n    newsletterManagement: {\n      enabled: true,\n    }\n  },\n  providers: {\n    default: 'broadcast',\n    broadcast: {\n      apiUrl: process.env.BROADCAST_API_URL,\n      token: process.env.BROADCAST_TOKEN,\n      fromAddress: 'newsletter@yoursite.com',\n      fromName: 'Your Newsletter',\n    }\n  }\n})\n```\n\nThis adds a `broadcasts` collection with:\n- Rich text editor with email-safe formatting\n- Image uploads with Media collection integration\n- Custom email blocks (buttons, dividers)\n- Inline email preview with React Email\n- Automatic sync with your email provider\n- Draft/publish system with scheduled publishing support\n\n### Send = Publish Workflow\n\nThe plugin integrates seamlessly with Payload's draft/publish system:\n\n- **Draft**: Create and edit broadcasts without sending\n- **Publish**: Publishing a broadcast automatically sends it via your configured email provider\n- **Schedule**: Use Payload's scheduled publishing to send broadcasts at a future time\n\n**How it works:**\n1. Create a broadcast and save as draft\n2. When ready, click \"Publish\" to send immediately\n3. Or use \"Schedule\" to publish (and send) at a specific date/time\n\n**Important**: Scheduled publishing requires configuring Payload's Jobs Queue. For Vercel deployments, add this to your `vercel.json`:\n\n```json\n{\n  \"crons\": [\n    {\n      \"path\": \"/api/payload-jobs/run\",\n      \"schedule\": \"*/5 * * * *\"\n    }\n  ]\n}\n```\n\nAnd secure the endpoint in your `payload.config.ts`:\n\n```typescript\nexport default buildConfig({\n  // ... other config\n  jobs: {\n    access: {\n      run: ({ req }) => {\n        if (req.user) return true\n        const authHeader = req.headers.get('authorization')\n        return authHeader === `Bearer ${process.env.CRON_SECRET}`\n      },\n    },\n  },\n})\n```\n\n### Custom Email Templates (v0.12.0+)\n\nCustomize your email design with React Email templates:\n\n```typescript\n// email-templates/broadcast-template.tsx\nimport { Html, Body, Container, Text, Link } from '@react-email/components'\n\nexport default function BroadcastTemplate({ subject, preheader, content }) {\n  return (\n    <Html>\n      <Body style={{ backgroundColor: '#ffffff', fontFamily: 'Arial, sans-serif' }}>\n        <Container style={{ maxWidth: '600px', margin: '0 auto' }}>\n          <Text style={{ fontSize: '16px', lineHeight: '1.6' }}>\n            <div dangerouslySetInnerHTML={{ __html: content }} />\n          </Text>\n          <hr style={{ margin: '40px 0', border: '1px solid #e5e7eb' }} />\n          <Text style={{ fontSize: '14px', color: '#6b7280', textAlign: 'center' }}>\n            <Link href=\"{{unsubscribe_url}}\" style={{ color: '#6b7280' }}>\n              Unsubscribe\n            </Link>\n          </Text>\n        </Container>\n      </Body>\n    </Html>\n  )\n}\n```\n\nThe plugin automatically detects templates at `email-templates/broadcast-template.tsx`.\n\n### Utilities\n\nConvert Lexical content to email-safe HTML:\n\n```typescript\nimport { convertToEmailSafeHtml } from 'payload-plugin-newsletter/utils'\n\nconst html = await convertToEmailSafeHtml(editorState)\n```\n\nValidate any HTML for email compatibility:\n\n```typescript\nimport { validateEmailHtml } from 'payload-plugin-newsletter/utils'\n\nconst result = validateEmailHtml(html)\nif (!result.valid) {\n  console.error('Email issues:', result.errors)\n}\n```\n\n## Configuration Options\n\n### Minimal Configuration\n\n```typescript\nnewsletterPlugin({\n  providers: {\n    default: 'resend',\n    resend: {\n      apiKey: process.env.RESEND_API_KEY,\n      fromAddress: 'newsletter@yoursite.com',\n      fromName: 'Your Newsletter',\n    },\n  },\n})\n```\n\n### Full Configuration\n\n```typescript\nnewsletterPlugin({\n  // Subscriber collection slug (default: 'subscribers')\n  subscribersSlug: 'newsletter-subscribers',\n  \n  // Email providers\n  providers: {\n    default: 'resend',\n    resend: {\n      apiKey: process.env.RESEND_API_KEY,\n      fromAddress: 'newsletter@yoursite.com',\n      fromName: 'Your Newsletter',\n      audienceIds: {\n        en: {\n          production: 'aud_prod_123',\n          development: 'aud_dev_123',\n        },\n        es: {\n          production: 'aud_prod_456',\n          development: 'aud_dev_456',\n        },\n      },\n    },\n  },\n  \n  // Magic link authentication\n  auth: {\n    enabled: true,\n    tokenExpiration: '7d', // How long magic links are valid\n    magicLinkPath: '/newsletter/verify', // Where to redirect for verification\n  },\n  \n  // Features\n  features: {\n    // Lead magnets (e.g., downloadable PDFs)\n    leadMagnets: {\n      enabled: true,\n      collection: 'media', // Which collection stores your lead magnets\n    },\n    \n    // Post-signup surveys\n    surveys: {\n      enabled: true,\n      questions: [\n        {\n          id: 'interests',\n          question: 'What topics interest you?',\n          type: 'multiselect',\n          options: ['Tech', 'Business', 'Design'],\n        },\n      ],\n    },\n    \n    // Newsletter scheduling for articles\n    newsletterScheduling: {\n      enabled: true,\n      articlesCollection: 'posts', // Your articles/posts collection\n    },\n    \n    // Broadcast management (v0.10.0+)\n    newsletterManagement: {\n      enabled: true, // Enables broadcasts collection\n    },\n    \n    // UTM tracking\n    utmTracking: {\n      enabled: true,\n      fields: ['source', 'medium', 'campaign', 'content', 'term'],\n    },\n  },\n  \n  // Internationalization\n  i18n: {\n    defaultLocale: 'en',\n    locales: ['en', 'es', 'fr'],\n  },\n  \n  // Custom hooks\n  hooks: {\n    afterSubscribe: async ({ doc, req }) => {\n      // Send to analytics, CRM, etc.\n      console.log('New subscriber:', doc.email)\n    },\n  },\n})\n```\n\n## API Endpoints\n\nThe plugin adds these endpoints to your application:\n\n### POST `/api/newsletter/subscribe`\nSubscribe a new email address\n\n```typescript\n// Request\n{\n  \"email\": \"user@example.com\",\n  \"name\": \"John Doe\", // optional\n  \"preferences\": { // optional\n    \"newsletter\": true,\n    \"announcements\": false\n  }\n}\n\n// Response\n{\n  \"success\": true,\n  \"subscriber\": { /* subscriber object */ }\n}\n```\n\n### POST `/api/newsletter/verify-magic-link`\nVerify a magic link token\n\n```typescript\n// Request\n{\n  \"token\": \"eyJhbGc...\"\n}\n\n// Response\n{\n  \"success\": true,\n  \"subscriber\": { /* subscriber object */ },\n  \"sessionToken\": \"eyJhbGc...\"\n}\n```\n\n### GET/POST `/api/newsletter/preferences`\nGet or update subscriber preferences (requires magic link auth)\n\n### POST `/api/newsletter/unsubscribe`\nUnsubscribe an email address\n\n### POST `/api/newsletter/signin`\nRequest a magic link for existing subscribers\n\n```typescript\n// Request\n{\n  \"email\": \"user@example.com\"\n}\n\n// Response\n{\n  \"success\": true,\n  \"message\": \"Check your email for the sign-in link\"\n}\n```\n\n### GET `/api/newsletter/me`\nGet current authenticated subscriber (requires authentication)\n\n```typescript\n// Response\n{\n  \"success\": true,\n  \"subscriber\": {\n    \"id\": \"123\",\n    \"email\": \"user@example.com\",\n    \"name\": \"John Doe\",\n    \"status\": \"active\",\n    \"preferences\": { /* preferences */ }\n  }\n}\n```\n\n### POST `/api/newsletter/signout`\nSign out the current subscriber\n\n```typescript\n// Response\n{\n  \"success\": true,\n  \"message\": \"Signed out successfully\"\n}\n```\n\n## Authentication\n\nThe plugin provides complete magic link authentication for subscribers:\n\n### Client-Side Authentication\n\nUse the `useNewsletterAuth` hook in your React components:\n\n```tsx\nimport { useNewsletterAuth } from 'payload-plugin-newsletter/client'\n\nfunction MyComponent() {\n  const { \n    subscriber, \n    isAuthenticated, \n    isLoading, \n    signOut, \n    refreshAuth \n  } = useNewsletterAuth()\n  \n  if (isLoading) return <div>Loading...</div>\n  \n  if (!isAuthenticated) {\n    return <div>Please sign in to manage your preferences</div>\n  }\n  \n  return (\n    <div>\n      <p>Welcome {subscriber.email}!</p>\n      <button onClick={signOut}>Sign Out</button>\n    </div>\n  )\n}\n```\n\n### Server-Side Authentication\n\nFor Next.js applications, use the session utilities:\n\n```typescript\nimport { requireAuth, getServerSideAuth } from 'payload-plugin-newsletter'\n\n// Protect a page - redirects to /auth/signin if not authenticated\nexport const getServerSideProps = requireAuth()\n\n// Or with custom logic\nexport const getServerSideProps = requireAuth(async (context) => {\n  // Your custom logic here\n  const data = await fetchData()\n  return { props: { data } }\n})\n\n// Manual authentication check\nexport const getServerSideProps = async (context) => {\n  const { subscriber, isAuthenticated } = await getServerSideAuth(context)\n  \n  if (!isAuthenticated) {\n    // Handle unauthenticated state\n  }\n  \n  return {\n    props: { subscriber }\n  }\n}\n```\n\n### Authentication Flow\n\n1. **Subscribe**: New users receive a magic link email to verify their email\n2. **Sign In**: Existing subscribers can request a new magic link via `/api/newsletter/signin`\n3. **Verify**: Clicking the magic link verifies the email and creates a session\n4. **Session**: Sessions are stored in httpOnly cookies (30-day expiry by default)\n5. **Sign Out**: Clears the session cookie\n\n### Configuration\n\n```typescript\nnewsletterPlugin({\n  auth: {\n    enabled: true, // Enable/disable authentication\n    tokenExpiration: '7d', // Magic link validity\n    magicLinkPath: '/newsletter/verify', // Verification redirect path\n  },\n  // Email templates can be customized\n  emails: {\n    magicLink: {\n      subject: 'Sign in to {{siteName}}',\n    },\n    welcome: {\n      enabled: true,\n      subject: 'Welcome to {{siteName}}!',\n    },\n    signIn: {\n      subject: 'Sign in to your account',\n    },\n  },\n})\n```\n\n## Newsletter Scheduling\n\nIf you enable newsletter scheduling, the plugin adds scheduling fields to your articles collection:\n\n```typescript\nfeatures: {\n  newsletterScheduling: {\n    enabled: true,\n    articlesCollection: 'articles', // Your existing collection\n  }\n}\n```\n\nThis adds a \"Newsletter Scheduling\" group to your articles with:\n- Schedule toggle\n- Send date/time picker\n- Audience segment selection\n- Send status tracking\n\n## Webhook Configuration (Broadcast)\n\nThe plugin supports real-time webhook integration with Broadcast for instant updates:\n\n### Automatic Updates\n\nWhen configured, the plugin automatically receives and processes:\n- **Subscriber Events**: `subscribed`, `unsubscribed`\n- **Broadcast Events**: All status changes (`scheduled`, `in_progress`, `sent`, etc.)\n\n### Setup Instructions\n\n1. **Save your Newsletter Settings** in the Payload admin to generate a webhook URL\n2. **Configure in Broadcast**:\n   - Go to your Broadcast dashboard → \"Webhook Endpoints\"\n   - Click \"Add Webhook Endpoint\"\n   - Paste the webhook URL from Payload\n   - Select events:\n     - Subscriber Events: `subscribed`, `unsubscribed`\n     - Broadcast Events: All\n   - Create the webhook and copy the webhook secret\n3. **Add the webhook secret** to your Newsletter Settings in Payload\n4. **Save and verify** the webhook connection\n\n### Security\n\nWebhooks are secured with:\n- HMAC-SHA256 signature verification\n- Timestamp validation (5-minute window)\n- Secret key stored in Newsletter Settings\n\n### Data Flow\n\n- **Subscriber events** update the subscriber's status and metadata\n- **Broadcast events** update the broadcast's status and delivery metrics\n- All updates happen in real-time without polling\n\n**Note**: Email engagement events (opens, clicks) remain in Broadcast for analytics.\n\n## Email Providers\n\n### Resend\n\n[Resend](https://resend.com) is a modern email API for developers.\n\n```typescript\nproviders: {\n  default: 'resend',\n  resend: {\n    apiKey: process.env.RESEND_API_KEY,\n    fromAddress: 'hello@yoursite.com',\n    fromName: 'Your Newsletter',\n    audienceIds: {\n      en: {\n        production: 'your_audience_id',\n      },\n    },\n  },\n}\n```\n\n### Broadcast\n\n[Broadcast](https://sendbroadcast.net/) is a self-hosted email automation platform.\n\n```typescript\nproviders: {\n  default: 'broadcast',\n  broadcast: {\n    apiUrl: process.env.BROADCAST_API_URL,\n    token: process.env.BROADCAST_TOKEN,\n    // Optional: These can be set here as defaults or configured in the admin UI\n    fromAddress: 'hello@yoursite.com',\n    fromName: 'Your Newsletter',\n    replyTo: 'replies@yoursite.com',\n  },\n}\n```\n\n**Note**: Settings configured in the Payload admin UI take precedence over these config values. The config values serve as defaults when settings haven't been configured yet.\n\n## TypeScript\n\nThe plugin is fully typed. Import types as needed:\n\n```typescript\nimport type { \n  NewsletterPluginConfig,\n  Subscriber,\n  EmailProvider \n} from 'payload-plugin-newsletter/types'\n```\n\n## Customization\n\n### Custom Fields\n\nAdd custom fields to the subscribers collection:\n\n```typescript\nnewsletterPlugin({\n  fields: {\n    additional: [\n      {\n        name: 'company',\n        type: 'text',\n        label: 'Company Name',\n      },\n      {\n        name: 'role',\n        type: 'select',\n        options: ['developer', 'designer', 'manager'],\n      },\n    ],\n  },\n})\n```\n\n### Custom Email Templates\n\nOverride the default email templates:\n\n```typescript\nimport { WelcomeEmail } from './emails/Welcome'\n\nnewsletterPlugin({\n  templates: {\n    welcome: WelcomeEmail,\n  },\n})\n```\n\n### Extending the Broadcasts Collection (v0.15.0+)\n\nYou can extend the Broadcasts collection with additional fields and custom email-compatible blocks:\n\n```typescript\nimport type { Block } from 'payload'\n\nconst customBlock: Block = {\n  slug: 'product-spotlight',\n  labels: { singular: 'Product Spotlight', plural: 'Product Spotlights' },\n  fields: [\n    { name: 'product', type: 'relationship', relationTo: 'products', required: true },\n    { name: 'description', type: 'textarea' }\n  ]\n}\n\nnewsletterPlugin({\n  // ... existing config\n  customizations: {\n    broadcasts: {\n      additionalFields: [\n        {\n          name: 'slug',\n          type: 'text',\n          required: true,\n          admin: { position: 'sidebar' }\n        }\n      ],\n      customBlocks: [customBlock], // Processed server-side for email compatibility\n      fieldOverrides: {\n        content: (defaultField) => ({\n          ...defaultField,\n          admin: {\n            ...defaultField.admin,\n            description: 'Custom description'\n          }\n        })\n      },\n      // Email preview customization (v0.20.0+)\n      emailPreview: {\n        // Disable default email template wrapping\n        wrapInTemplate: false,\n        \n        // Or provide a custom wrapper function\n        customWrapper: async (content, { subject, preheader }) => {\n          return `\n            <div class=\"my-custom-template\">\n              <h1>${subject}</h1>\n              ${preheader ? `<p class=\"preheader\">${preheader}</p>` : ''}\n              <div class=\"content\">${content}</div>\n            </div>\n          `\n        }\n      }\n    }\n  }\n})\n```\n\n**Note**: Custom blocks are processed server-side to ensure email compatibility and prevent Next.js serialization errors.\n\n### Email Preview Customization (v0.20.0+)\n\nThe plugin now supports full customization of email preview rendering. This is useful when you have custom email templates and want the preview to match what's actually sent.\n\n#### Disable Default Template Wrapping\n\nIf you're using your own email template system, you can disable the default template wrapping:\n\n```typescript\nnewsletterPlugin({\n  customizations: {\n    broadcasts: {\n      emailPreview: {\n        wrapInTemplate: false // Show raw HTML without email template\n      }\n    }\n  }\n})\n```\n\n#### Custom Email Wrapper\n\nProvide your own wrapper function to match your email service's template:\n\n```typescript\nnewsletterPlugin({\n  customizations: {\n    broadcasts: {\n      emailPreview: {\n        customWrapper: async (content, { subject, preheader }) => {\n          // Return your custom email template\n          return `\n            <!DOCTYPE html>\n            <html>\n              <head>\n                <title>${subject}</title>\n                <!-- Your custom styles -->\n              </head>\n              <body>\n                <div class=\"preheader\">${preheader}</div>\n                ${content}\n                <!-- Your footer -->\n              </body>\n            </html>\n          `\n        }\n      }\n    }\n  }\n})\n```\n\n#### Advanced: Using with React Email\n\nIf you're using React Email for templates, you can integrate it with the preview:\n\n```typescript\nimport { render } from '@react-email/render'\nimport { MyEmailTemplate } from './emails/MyEmailTemplate'\n\nnewsletterPlugin({\n  customizations: {\n    broadcasts: {\n      emailPreview: {\n        customWrapper: async (content, { subject, preheader }) => {\n          return await render(\n            <MyEmailTemplate \n              subject={subject}\n              preheader={preheader}\n              content={content}\n            />\n          )\n        }\n      }\n    }\n  }\n})\n```\n\nThis ensures your preview exactly matches what subscribers will see.\n\nFor complete extensibility documentation, see the [Extension Points Guide](./docs/architecture/extension-points.md).\n\n## Troubleshooting\n\n### Common Issues\n\n**\"Already subscribed\" error**\n- The email already exists in the subscribers collection\n- Check the admin panel to manage existing subscribers\n\n**Magic links not working**\n- Ensure `JWT_SECRET` is set in your environment variables\n- Check that the `magicLinkPath` matches your frontend route\n\n**Emails not sending**\n- Verify your API keys are correct\n- Check the email provider's dashboard for errors\n- Ensure from address is verified with your provider\n\n## Security\n\n### Access Control\n\nThe plugin implements proper access control for all operations:\n\n- **Subscriber data**: Users can only access and modify their own data via magic link authentication\n- **Newsletter settings**: Only admin users can modify email provider settings and configurations\n- **API endpoints**: All endpoints respect Payload's access control rules\n\n#### Custom Admin Check\n\nThe plugin supports multiple admin authentication patterns out of the box:\n- `user.roles.includes('admin')` - Role-based\n- `user.isAdmin === true` - Boolean field\n- `user.role === 'admin'` - Single role field\n- `user.admin === true` - Admin boolean\n\nIf your setup uses a different pattern, configure a custom admin check:\n\n```typescript\nnewsletterPlugin({\n  access: {\n    isAdmin: (user) => {\n      // Your custom logic\n      return user.customAdminField === true\n    }\n  },\n  // ... other config\n})\n```\n\n### Best Practices\n\n- Always use environment variables for sensitive data (API keys, JWT secrets)\n- Enable double opt-in for GDPR compliance\n- Configure allowed domains to prevent spam subscriptions\n- Set reasonable rate limits for subscriptions per IP\n\n## Migration Guide\n\nComing from another newsletter system? The plugin stores subscribers in a standard Payload collection, making it easy to import existing data:\n\n```typescript\n// Example migration script\nconst existingSubscribers = await getFromOldSystem()\n\nfor (const subscriber of existingSubscribers) {\n  await payload.create({\n    collection: 'subscribers',\n    data: {\n      email: subscriber.email,\n      name: subscriber.name,\n      subscriptionStatus: 'active',\n      // Map other fields as needed\n    },\n  })\n}\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [feedback and contribution guide](./FEEDBACK.md).\n\n### Release Process\n\nThis project uses a developer-controlled release process:\n- **Version bumps happen locally** - You control when and what type\n- **CI/CD publishes automatically** - When it detects a version change\n- **No bot commits** - Your local repo stays in sync\n\nSee [Release Documentation](./docs/RELEASE.md) for details.\n\n## License\n\nMIT",
  "bytes": 26207,
  "sha": "6b3e2aa9192f795f6e3516b084632ea051fae6bee070ba64aba53e2c36e3bdfe",
  "repo_slug": "aniketpanjwani/payload-plugin-email-newsletter",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/skl_aniketpanjwani_payload_plugin_email_news_eefbdcde/readme"
}