UNPKG

payload-plugin-newsletter

Version:

Complete newsletter management plugin for Payload CMS with subscriber management, magic link authentication, and email service integration

4,912 lines 163 kB
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/types/newsletter.ts
var NewsletterProviderError;
var init_newsletter = __esm({
  "src/types/newsletter.ts"() {
    "use strict";
    NewsletterProviderError = class extends Error {
      constructor(message, code, provider, details) {
        super(message);
        this.code = code;
        this.provider = provider;
        this.details = details;
        this.name = "NewsletterProviderError";
      }
    };
  }
});

// src/types/broadcast.ts
var BroadcastProviderError;
var init_broadcast = __esm({
  "src/types/broadcast.ts"() {
    "use strict";
    init_newsletter();
    BroadcastProviderError = class extends Error {
      constructor(message, code, provider, details) {
        super(message);
        this.code = code;
        this.provider = provider;
        this.details = details;
        this.name = "BroadcastProviderError";
      }
    };
  }
});

// src/types/channel.ts
var init_channel = __esm({
  "src/types/channel.ts"() {
    "use strict";
  }
});

// src/types/providers.ts
var BaseBroadcastProvider;
var init_providers = __esm({
  "src/types/providers.ts"() {
    "use strict";
    init_broadcast();
    init_newsletter();
    BaseBroadcastProvider = class {
      constructor(config) {
        this.config = config;
      }
      /**
       * Schedule a broadcast - default implementation throws not supported
       */
      async schedule(_id, _scheduledAt) {
        const capabilities = this.getCapabilities();
        if (!capabilities.supportsScheduling) {
          throw new BroadcastProviderError(
            "Scheduling is not supported by this provider",
            "NOT_SUPPORTED" /* NOT_SUPPORTED */,
            this.name
          );
        }
        throw new Error("Method not implemented");
      }
      /**
       * Cancel scheduled broadcast - default implementation throws not supported
       */
      async cancelSchedule(_id) {
        const capabilities = this.getCapabilities();
        if (!capabilities.supportsScheduling) {
          throw new BroadcastProviderError(
            "Scheduling is not supported by this provider",
            "NOT_SUPPORTED" /* NOT_SUPPORTED */,
            this.name
          );
        }
        throw new Error("Method not implemented");
      }
      /**
       * Get analytics - default implementation returns zeros
       */
      async getAnalytics(_id) {
        const capabilities = this.getCapabilities();
        if (!capabilities.supportsAnalytics) {
          throw new BroadcastProviderError(
            "Analytics are not supported by this provider",
            "NOT_SUPPORTED" /* NOT_SUPPORTED */,
            this.name
          );
        }
        return {
          sent: 0,
          delivered: 0,
          opened: 0,
          clicked: 0,
          bounced: 0,
          complained: 0,
          unsubscribed: 0
        };
      }
      /**
       * Helper method to validate required fields
       */
      validateRequiredFields(data, fields) {
        const missing = fields.filter((field) => !data[field]);
        if (missing.length > 0) {
          throw new BroadcastProviderError(
            `Missing required fields: ${missing.join(", ")}`,
            "VALIDATION_ERROR" /* VALIDATION_ERROR */,
            this.name
          );
        }
      }
      /**
       * Helper method to check if a status transition is allowed
       */
      canEditInStatus(status) {
        const capabilities = this.getCapabilities();
        return capabilities.editableStatuses.includes(status);
      }
      /**
       * Helper to build pagination response
       */
      buildListResponse(items, total, options = {}) {
        const limit = options.limit || 20;
        const offset = options.offset || 0;
        return {
          items,
          total,
          limit,
          offset,
          hasMore: offset + items.length < total
        };
      }
    };
  }
});

// src/types/index.ts
var init_types = __esm({
  "src/types/index.ts"() {
    "use strict";
    init_broadcast();
    init_channel();
    init_providers();
    init_newsletter();
  }
});

// src/providers/broadcast/broadcast.ts
var broadcast_exports = {};
__export(broadcast_exports, {
  BroadcastApiProvider: () => BroadcastApiProvider
});
var BroadcastApiProvider;
var init_broadcast2 = __esm({
  "src/providers/broadcast/broadcast.ts"() {
    "use strict";
    init_types();
    BroadcastApiProvider = class extends BaseBroadcastProvider {
      constructor(config) {
        super(config);
        this.name = "broadcast";
        this.apiUrl = config.apiUrl.replace(/\/$/, "");
        this.isDevelopment = process.env.NODE_ENV !== "production";
        this.token = this.isDevelopment ? config.tokens.development || config.tokens.production || "" : config.tokens.production || config.tokens.development || "";
        if (!this.token) {
          throw new BroadcastProviderError(
            "Broadcast API token is required",
            "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
            this.name
          );
        }
      }
      // Channel Management Methods
      async listChannels(options) {
        try {
          const params = new URLSearchParams();
          if (options?.limit) params.append("limit", options.limit.toString());
          if (options?.offset) params.append("offset", options.offset.toString());
          const response = await fetch(`${this.apiUrl}/api/v1/channels?${params}`, {
            method: "GET",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const data = await response.json();
          const channels = data.data.map((channel) => this.transformChannelFromApi(channel));
          return {
            channels,
            total: data.total || channels.length,
            limit: options?.limit || 20,
            offset: options?.offset || 0
          };
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to list channels: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async getChannel(id) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/channels/${id}`, {
            method: "GET",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            if (response.status === 404) {
              throw new BroadcastProviderError(
                `Channel not found: ${id}`,
                "CHANNEL_NOT_FOUND" /* CHANNEL_NOT_FOUND */,
                this.name
              );
            }
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const channel = await response.json();
          return this.transformChannelFromApi(channel);
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to get channel: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async createChannel(data) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/channels`, {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              channel: {
                name: data.name,
                description: data.description,
                from: data.fromName,
                address: data.fromEmail,
                reply_to: data.replyTo
              }
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const channel = await response.json();
          return this.transformChannelFromApi(channel);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to create channel: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async updateChannel(id, data) {
        try {
          const updateData = { channel: {} };
          if (data.name !== void 0) updateData.channel.name = data.name;
          if (data.description !== void 0) updateData.channel.description = data.description;
          if (data.fromName !== void 0) updateData.channel.from = data.fromName;
          if (data.fromEmail !== void 0) updateData.channel.address = data.fromEmail;
          if (data.replyTo !== void 0) updateData.channel.reply_to = data.replyTo;
          const response = await fetch(`${this.apiUrl}/api/v1/channels/${id}`, {
            method: "PATCH",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify(updateData)
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const channel = await response.json();
          return this.transformChannelFromApi(channel);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to update channel: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async deleteChannel(id) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/channels/${id}`, {
            method: "DELETE",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to delete channel: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      // Broadcast Management Methods
      async list(options) {
        try {
          const params = new URLSearchParams();
          if (options?.limit) params.append("limit", options.limit.toString());
          if (options?.offset) params.append("offset", options.offset.toString());
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts?${params}`, {
            method: "GET",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const data = await response.json();
          const broadcasts = data.data.map((broadcast) => this.transformBroadcastFromApi(broadcast));
          return this.buildListResponse(broadcasts, data.total, options);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to list broadcasts: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async get(id) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}`, {
            method: "GET",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            if (response.status === 404) {
              throw new BroadcastProviderError(
                `Broadcast not found: ${id}`,
                "NOT_FOUND" /* NOT_FOUND */,
                this.name
              );
            }
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const broadcast = await response.json();
          return this.transformBroadcastFromApi(broadcast);
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to get broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async create(data) {
        try {
          this.validateRequiredFields(data, ["channelId", "name", "subject", "content"]);
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts`, {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              broadcast: {
                channel_id: parseInt(data.channelId),
                // Broadcast API uses numeric IDs
                name: data.name,
                subject: data.subject,
                preheader: data.preheader,
                body: data.content,
                html_body: true,
                track_opens: data.trackOpens ?? true,
                track_clicks: data.trackClicks ?? true,
                reply_to: data.replyTo,
                segment_ids: data.audienceIds
              }
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const result = await response.json();
          return this.get(result.id.toString());
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to create broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async update(id, data) {
        try {
          const existing = await this.get(id);
          if (!this.canEditInStatus(existing.status)) {
            throw new BroadcastProviderError(
              `Cannot update broadcast in status: ${existing.status}`,
              "INVALID_STATUS" /* INVALID_STATUS */,
              this.name
            );
          }
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}`, {
            method: "PATCH",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              broadcast: {
                name: data.name,
                subject: data.subject,
                preheader: data.preheader,
                body: data.content,
                track_opens: data.trackOpens,
                track_clicks: data.trackClicks,
                reply_to: data.replyTo,
                segment_ids: data.audienceIds
              }
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const broadcast = await response.json();
          return this.transformBroadcastFromApi(broadcast);
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to update broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async delete(id) {
        try {
          const existing = await this.get(id);
          if (!this.canEditInStatus(existing.status)) {
            throw new BroadcastProviderError(
              `Cannot delete broadcast in status: ${existing.status}`,
              "INVALID_STATUS" /* INVALID_STATUS */,
              this.name
            );
          }
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}`, {
            method: "DELETE",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            }
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to delete broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async send(id, options) {
        try {
          if (options?.testMode && options.testRecipients?.length) {
            throw new BroadcastProviderError(
              "Test send is not yet implemented for Broadcast provider",
              "NOT_SUPPORTED" /* NOT_SUPPORTED */,
              this.name
            );
          }
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}/send_broadcast`, {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              segment_ids: options?.audienceIds
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const result = await response.json();
          return this.get(result.id.toString());
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to send broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async schedule(id, scheduledAt) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}`, {
            method: "PATCH",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              broadcast: {
                scheduled_send_at: scheduledAt.toISOString(),
                // TODO: Handle timezone properly
                scheduled_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
              }
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const broadcast = await response.json();
          return this.transformBroadcastFromApi(broadcast);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to schedule broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async cancelSchedule(id) {
        try {
          const response = await fetch(`${this.apiUrl}/api/v1/broadcasts/${id}`, {
            method: "PATCH",
            headers: {
              "Authorization": `Bearer ${this.token}`,
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              broadcast: {
                scheduled_send_at: null,
                scheduled_timezone: null
              }
            })
          });
          if (!response.ok) {
            const error = await response.text();
            throw new Error(`Broadcast API error: ${response.status} - ${error}`);
          }
          const broadcast = await response.json();
          return this.transformBroadcastFromApi(broadcast);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to cancel scheduled broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async getAnalytics(_id) {
        throw new BroadcastProviderError(
          "Analytics API not yet implemented for Broadcast provider",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      getCapabilities() {
        return {
          supportsScheduling: true,
          supportsSegmentation: true,
          supportsAnalytics: false,
          // Not documented yet
          supportsABTesting: false,
          supportsTemplates: false,
          supportsPersonalization: true,
          supportsMultipleChannels: true,
          supportsChannelSegmentation: true,
          editableStatuses: ["draft" /* DRAFT */, "scheduled" /* SCHEDULED */],
          supportedContentTypes: ["html", "text"]
        };
      }
      async validateConfiguration() {
        try {
          await this.list({ limit: 1 });
          return true;
        } catch {
          return false;
        }
      }
      transformBroadcastFromApi(broadcast) {
        return {
          id: broadcast.id.toString(),
          channelId: "1",
          // TODO: Get from API response when available
          name: broadcast.name,
          subject: broadcast.subject,
          preheader: broadcast.preheader,
          content: broadcast.body,
          status: this.mapBroadcastStatus(broadcast.status),
          trackOpens: broadcast.track_opens,
          trackClicks: broadcast.track_clicks,
          replyTo: broadcast.reply_to,
          recipientCount: broadcast.total_recipients,
          sentAt: broadcast.sent_at ? new Date(broadcast.sent_at) : void 0,
          scheduledAt: broadcast.scheduled_send_at ? new Date(broadcast.scheduled_send_at) : void 0,
          createdAt: new Date(broadcast.created_at),
          updatedAt: new Date(broadcast.updated_at),
          providerData: { broadcast },
          providerId: broadcast.id.toString(),
          providerType: "broadcast"
        };
      }
      transformChannelFromApi(channel) {
        return {
          id: channel.id.toString(),
          name: channel.name,
          description: channel.description,
          fromName: channel.from,
          fromEmail: channel.address,
          replyTo: channel.reply_to,
          providerId: channel.id.toString(),
          providerType: "broadcast",
          subscriberCount: channel.total_active_subscribers,
          active: true,
          // Broadcast API doesn't have an active field
          createdAt: new Date(channel.created_at),
          updatedAt: new Date(channel.updated_at)
        };
      }
      mapBroadcastStatus(status) {
        const statusMap = {
          "draft": "draft" /* DRAFT */,
          "scheduled": "scheduled" /* SCHEDULED */,
          "queueing": "sending" /* SENDING */,
          "sending": "sending" /* SENDING */,
          "sent": "sent" /* SENT */,
          "failed": "failed" /* FAILED */,
          "partial_failure": "failed" /* FAILED */,
          "paused": "paused" /* PAUSED */,
          "aborted": "canceled" /* CANCELED */
        };
        return statusMap[status] || "draft" /* DRAFT */;
      }
    };
  }
});

// src/providers/resend/broadcast.ts
var broadcast_exports2 = {};
__export(broadcast_exports2, {
  ResendBroadcastProvider: () => ResendBroadcastProvider
});
var import_resend3, ResendBroadcastProvider;
var init_broadcast3 = __esm({
  "src/providers/resend/broadcast.ts"() {
    "use strict";
    import_resend3 = require("resend");
    init_types();
    ResendBroadcastProvider = class extends BaseBroadcastProvider {
      constructor(config) {
        super(config);
        this.name = "resend";
        this.client = new import_resend3.Resend(config.apiKey);
        this.audienceIds = config.audienceIds || {};
        this.isDevelopment = process.env.NODE_ENV !== "production";
        if (!config.apiKey) {
          throw new BroadcastProviderError(
            "Resend API key is required",
            "CONFIGURATION_ERROR" /* CONFIGURATION_ERROR */,
            this.name
          );
        }
      }
      // Channel Management Methods (map to Resend Audiences)
      async listChannels(options) {
        try {
          const response = await this.client.audiences.list();
          const channels = response.data?.data?.map((audience) => ({
            id: audience.id,
            name: audience.name,
            description: void 0,
            // Resend doesn't have description
            fromName: this.config.fromName || "",
            fromEmail: this.config.fromEmail || "",
            replyTo: this.config.replyTo,
            providerId: audience.id,
            providerType: "resend",
            subscriberCount: void 0,
            // Not available in list API
            active: true,
            createdAt: new Date(audience.created_at),
            updatedAt: new Date(audience.created_at)
            // No updated_at in Resend
          })) || [];
          return {
            channels,
            total: channels.length,
            limit: options?.limit || 100,
            offset: options?.offset || 0
          };
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to list channels (audiences): ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async getChannel(id) {
        try {
          const response = await this.client.audiences.get(id);
          if (!response.data) {
            throw new BroadcastProviderError(
              `Channel (audience) not found: ${id}`,
              "CHANNEL_NOT_FOUND" /* CHANNEL_NOT_FOUND */,
              this.name
            );
          }
          return {
            id: response.data.id,
            name: response.data.name,
            description: void 0,
            fromName: this.config.fromName || "",
            fromEmail: this.config.fromEmail || "",
            replyTo: this.config.replyTo,
            providerId: response.data.id,
            providerType: "resend",
            subscriberCount: void 0,
            // Not available
            active: true,
            createdAt: new Date(response.data.created_at),
            updatedAt: new Date(response.data.created_at)
          };
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to get channel (audience): ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async createChannel(data) {
        try {
          const response = await this.client.audiences.create({
            name: data.name
          });
          if (!response.data) {
            throw new Error("Failed to create audience");
          }
          return {
            id: response.data.id,
            name: response.data.name,
            description: data.description,
            fromName: data.fromName,
            fromEmail: data.fromEmail,
            replyTo: data.replyTo,
            providerId: response.data.id,
            providerType: "resend",
            subscriberCount: 0,
            active: true,
            createdAt: /* @__PURE__ */ new Date(),
            updatedAt: /* @__PURE__ */ new Date()
          };
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to create channel (audience): ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async updateChannel(_id, _data) {
        throw new BroadcastProviderError(
          "Updating channels (audiences) is not supported by Resend API",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async deleteChannel(id) {
        try {
          await this.client.audiences.remove(id);
        } catch (error) {
          throw new BroadcastProviderError(
            `Failed to delete channel (audience): ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      // Broadcast Management Methods
      async list(_options) {
        throw new BroadcastProviderError(
          "Listing broadcasts is not currently supported by Resend API. This feature may be available in the dashboard only.",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async get(_id) {
        throw new BroadcastProviderError(
          "Getting individual broadcasts is not currently supported by Resend API. This feature may be available in the dashboard only.",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async create(data) {
        try {
          this.validateRequiredFields(data, ["channelId", "name", "subject", "content"]);
          const locale = "en";
          const audienceConfig = this.audienceIds?.[locale];
          const audienceId = this.isDevelopment ? audienceConfig?.development || audienceConfig?.production : audienceConfig?.production || audienceConfig?.development;
          if (!audienceId && data.audienceIds?.length) {
          }
          const resendClient = this.client;
          if (resendClient.broadcasts?.create) {
            const broadcast = await resendClient.broadcasts.create({
              name: data.name,
              subject: data.subject,
              from: `${this.config.fromName || "Newsletter"} <${this.config.fromEmail || "noreply@example.com"}>`,
              reply_to: data.replyTo,
              audience_id: audienceId || data.audienceIds?.[0],
              content: {
                html: data.content
                // TODO: Handle plain text version
              }
            });
            return this.transformResendToBroadcast(broadcast);
          }
          throw new BroadcastProviderError(
            "Creating broadcasts via API is not currently supported. Please check if Resend has released their Broadcasts API.",
            "NOT_SUPPORTED" /* NOT_SUPPORTED */,
            this.name
          );
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to create broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async update(_id, _data) {
        throw new BroadcastProviderError(
          "Updating broadcasts is not currently supported by Resend API. Note: Resend broadcasts can only be edited where they were created.",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async delete(_id) {
        throw new BroadcastProviderError(
          "Deleting broadcasts is not currently supported by Resend API.",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async send(id, options) {
        try {
          const resendClient = this.client;
          if (resendClient.broadcasts?.send) {
            await resendClient.broadcasts.send(id, {
              audience_id: options?.audienceIds?.[0]
              // TODO: Handle test mode if supported
            });
            return {
              id,
              channelId: options?.audienceIds?.[0] || "1",
              name: "Unknown",
              subject: "Unknown",
              content: "",
              status: "sending" /* SENDING */,
              trackOpens: true,
              trackClicks: true,
              createdAt: /* @__PURE__ */ new Date(),
              updatedAt: /* @__PURE__ */ new Date(),
              providerType: "resend"
            };
          }
          throw new BroadcastProviderError(
            "Sending broadcasts via API is not currently supported. Please check if Resend has released their Broadcasts API.",
            "NOT_SUPPORTED" /* NOT_SUPPORTED */,
            this.name
          );
        } catch (error) {
          if (error instanceof BroadcastProviderError) throw error;
          throw new BroadcastProviderError(
            `Failed to send broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
            "PROVIDER_ERROR" /* PROVIDER_ERROR */,
            this.name,
            error
          );
        }
      }
      async schedule(_id, _scheduledAt) {
        throw new BroadcastProviderError(
          "Scheduling broadcasts is not supported by Resend",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      async getAnalytics(_id) {
        throw new BroadcastProviderError(
          "Getting broadcast analytics via API is not currently supported. Analytics may be available in the Resend dashboard.",
          "NOT_SUPPORTED" /* NOT_SUPPORTED */,
          this.name
        );
      }
      getCapabilities() {
        return {
          supportsScheduling: false,
          // Not documented
          supportsSegmentation: true,
          // Via Audiences
          supportsAnalytics: true,
          // Available in dashboard, API unclear
          supportsABTesting: false,
          supportsTemplates: false,
          // Not clear from docs
          supportsPersonalization: true,
          // Via merge tags
          supportsMultipleChannels: true,
          // Via multiple audiences
          supportsChannelSegmentation: false,
          // Not within a single audience
          editableStatuses: [],
          // Unclear which statuses can be edited
          supportedContentTypes: ["html"]
          // React components via SDK
        };
      }
      async validateConfiguration() {
        try {
          const resendClient = this.client;
          if (resendClient.audiences?.list) {
            await resendClient.audiences.list({ limit: 1 });
            return true;
          }
          await this.client.emails.send({
            from: "onboarding@resend.dev",
            to: "delivered@resend.dev",
            subject: "Configuration Test",
            html: "<p>Testing configuration</p>"
          });
          return true;
        } catch {
          return false;
        }
      }
      /**
       * Transform Resend broadcast to our Broadcast type
       * NOTE: This is speculative based on what the API might return
       */
      transformResendToBroadcast(broadcast) {
        return {
          id: broadcast.id,
          channelId: broadcast.audience_id || "1",
          // Map audience_id to channelId
          name: broadcast.name || "Untitled",
          subject: broadcast.subject,
          preheader: broadcast.preheader,
          content: broadcast.content?.html || broadcast.html || "",
          status: this.mapResendStatus(broadcast.status),
          trackOpens: true,
          // Resend tracks by default
          trackClicks: true,
          // Resend tracks by default
          replyTo: broadcast.reply_to,
          recipientCount: broadcast.recipient_count,
          sentAt: broadcast.sent_at ? new Date(broadcast.sent_at) : void 0,
          scheduledAt: broadcast.scheduled_at ? new Date(broadcast.scheduled_at) : void 0,
          createdAt: new Date(broadcast.created_at || Date.now()),
          updatedAt: new Date(broadcast.updated_at || Date.now()),
          providerData: { broadcast },
          providerId: broadcast.id,
          providerType: "resend"
        };
      }
      mapResendStatus(status) {
        if (!status) return "draft" /* DRAFT */;
        const statusMap = {
          "draft": "draft" /* DRAFT */,
          "scheduled": "scheduled" /* SCHEDULED */,
          "sending": "sending" /* SENDING */,
          "sent": "sent" /* SENT */,
          "failed": "failed" /* FAILED */
        };
        return statusMap[status.toLowerCase()] || "draft" /* DRAFT */;
      }
    };
  }
});

// src/index.ts
var src_exports = {};
__export(src_exports, {
  default: () => newsletterPlugin,
  getServerSideAuth: () => getServerSideAuth,
  getTokenFromRequest: () => getTokenFromRequest,
  isAuthenticated: () => isAuthenticated,
  newsletterPlugin: () => newsletterPlugin,
  requireAuth: () => requireAuth,
  verifyToken: () => verifyToken
});
module.exports = __toCommonJS(src_exports);

// src/utils/access.ts
var isAdmin = (user, config) => {
  if (!user || user.collection !== "users") {
    return false;
  }
  if (config?.access?.isAdmin) {
    return config.access.isAdmin(user);
  }
  if (user.roles?.includes("admin")) {
    return true;
  }
  if (user.isAdmin === true) {
    return true;
  }
  if (user.role === "admin") {
    return true;
  }
  if (user.admin === true) {
    return true;
  }
  return false;
};
var adminOnly = (config) => ({ req }) => {
  const user = req.user;
  return isAdmin(user, config);
};
var adminOrSelf = (config) => ({ req, id }) => {
  const user = req.user;
  if (!user) {
    if (!id) {
      return {
        id: {
          equals: "unauthorized-no-access"
        }
      };
    }
    return false;
  }
  if (isAdmin(user, config)) {
    return true;
  }
  if (user.collection === "subscribers") {
    if (!id) {
      return {
        id: {
          equals: user.id
        }
      };
    }
    return id === user.id;
  }
  if (!id) {
    return {
      id: {
        equals: "unauthorized-no-access"
      }
    };
  }
  return false;
};

// src/emails/render.tsx
var import_render = require("@react-email/render");

// src/emails/MagicLink.tsx
var import_components = require("@react-email/components");

// src/emails/styles.ts
var styles = {
  main: {
    backgroundColor: "#f6f9fc",
    fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif'
  },
  container: {
    backgroundColor: "#ffffff",
    border: "1px solid #f0f0f0",
    borderRadius: "5px",
    margin: "0 auto",
    padding: "45px",
    marginBottom: "64px",
    maxWidth: "500px"
  },
  heading: {
    fontSize: "24px",
    letterSpacing: "-0.5px",
    lineHeight: "1.3",
    fontWeight: "600",
    color: "#484848",
    margin: "0 0 20px",
    padding: "0"
  },
  text: {
    fontSize: "16px",
    lineHeight: "26px",
    fontWeight: "400",
    color: "#484848",
    margin: "16px 0"
  },
  button: {
    backgroundColor: "#000000",
    borderRadius: "5px",
    color: "#fff",
    fontSize: "16px",
    fontWeight: "bold",
    textDecoration: "none",
    textAlign: "center",
    display: "block",
    width: "100%",
    padding: "14px 20px",
    margin: "30px 0"
  },
  link: {
    color: "#2754C5",
    fontSize: "14px",
    textDecoration: "underline",
    wordBreak: "break-all"
  },
  hr: {
    borderColor: "#e6ebf1",
    margin: "30px 0"
  },
  footer: {
    fontSize: "14px",
    lineHeight: "24px",
    color: "#9ca2ac",
    textAlign: "center",
    margin: "0"
  },
  code: {
    display: "inline-block",
    padding: "16px",
    width: "100%",
    backgroundColor: "#f4f4f4",
    borderRadius: "5px",
    border: "1px solid #eee",
    fontSize: "14px",
    fontFamily: "monospace",
    textAlign: "center",
    margin: "24px 0"
  }
};

// src/emails/MagicLink.tsx
var import_jsx_runtime = require("react/jsx-runtime");
var MagicLinkEmail = ({
  magicLink,
  email,
  siteName = "Newsletter",
  expiresIn = "24 hours"
}) => {
  const previewText = `Sign in to ${siteName}`;
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Html, { children: [
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Head, {}),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Preview, { children: previewText }),
    /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Body, { style: styles.main, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Container, { style: styles.container, children: [
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.heading, children: [
        "Sign in to ",
        siteName
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.text, children: [
        "Hi ",
        email.split("@")[0],
        ","
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.text, children: [
        "We received a request to sign in to your ",
        siteName,
        " account. Click the button below to complete your sign in:"
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Button, { href: magicLink, style: styles.button, children: [
        "Sign in to ",
        siteName
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Text, { style: styles.text, children: "Or copy and paste this URL into your browser:" }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: styles.code, children: magicLink }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Hr, { style: styles.hr }),
      /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_components.Text, { style: styles.footer, children: [
        "This link will expire in ",
        expiresIn,
        ". If you didn't request this email, you can safely ignore it."
      ] })
    ] }) })
  ] });
};

// src/emails/Welcome.tsx
var import_components2 = require("@react-email/components");
var import_jsx_runtime2 = require("react/jsx-runtime");
var WelcomeEmail = ({
  email,
  siteName = "Newsletter",
  preferencesUrl
}) => {
  const previewText = `Welcome to ${siteName}!`;
  const firstName = email.split("@")[0];
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Html, { children: [
    /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Head, {}),
    /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Preview, { children: previewText }),
    /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Body, { style: styles.main, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Container, { style: styles.container, children: [
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.heading, children: [
        "Welcome to ",
        siteName,
        "! \u{1F389}"
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
        "Hi ",
        firstName,
        ","
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
        "Thanks for subscribing to ",
        siteName,
        "! We're excited to have you as part of our community."
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.text, children: "You'll receive our newsletter based on your preferences. Speaking of which, you can update your preferences anytime:" }),
      preferencesUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Button, { href: preferencesUrl, style: styles.button, children: "Manage Preferences" }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.text, children: "Here's what you can expect from us:" }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_components2.Text, { style: styles.text, children: [
        "\u2022 Regular updates based on your chosen frequency",
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
        "\u2022 Content tailored to your interests",
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
        "\u2022 Easy unsubscribe options in every email",
        /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("br", {}),
        "\u2022 Your privacy respected always"
      ] }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Hr, { style: styles.hr }),
      /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_components2.Text, { style: styles.footer, children: "If you have any questions, feel free to reply to this email. We're here to help!" })
    ] }) })
  ] });
};

// src/emails/SignIn.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
var SignInEmail = (props) => {
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MagicLinkEmail, { ...props });
};

// src/emails/render.tsx
var import_jsx_runtime4 = require("react/jsx-runtime");
async function renderEmail(template, data) {
  try {
    switch (template) {
      case "magic-link": {
        const magicLinkData = data;
        return (0, import_render.render)(
          /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
            MagicLinkEmail,
            {
              magicLink: magicLinkData.magicLink || magicLinkData.verificationUrl || magicLinkData.magicLinkUrl || "",
              email: magicLinkData.email || "",
              siteName: magicLinkData.siteName,
              expiresIn: magicLinkData.expiresIn
            }
          )
        );
      }
      case "signin": {
        const signinData = data;
        return (0, import_render.render)(
          /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
            SignInEmail,
            {
              magicLink: signinData.magicLink || signinData.verificationUrl || signinData.magicLinkUrl || "",
              email: signinData.email || "",
              siteName: signinData.siteName,
              expiresIn: signinData.expiresIn
            }
          )
        );
      }
      case "welcome": {
        const welcomeData = data;
        return (0, import_render.render)(
          /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
            WelcomeEmail,
            {
              email: welcomeData.email || "",
              siteName: welcomeData.siteName,
              preferencesUrl: welcomeData.preferencesUrl
            }
          )
        );
      }
      default:
        throw new Error(`Unknown email template: ${template}`);
    }
  } catch (error) {
    console.error(`Failed to render email template ${template}:`, error);
    throw error;
  }
}

// src/collections/Subscribers.ts
var createSubscribersCollection = (pluginConfig) => {
  const slug = pluginConfig.subscribersSlug || "subscribers";
  const defaultFields = [
    // Core fields
    {
      name: "email",
      type: "email",
      required: true,
      unique: true,
      admin: {
        description: "Subscriber email address"
      }
    },
    {
      name: "name",
      type: "text",
      admin: {
        description: "Subscriber full name"
      }
    },
    {
      name: "locale",
      type: "select",
      options: pluginConfig.i18n?.locales?.map((locale) => ({
        label: locale.toUpperCase(),
        value: locale
      })) || [
        { label: "EN", value: "en" }
      ],
      defaultValue: pluginConfig.i18n?.defaultLocale || "en",
      admin: {
        description: "Preferred language for communications"
      }
    },
    // Authentication fields (hidden from admin UI)
    {
      name: "magicLinkToken",
      type: "text",
      hidden: true
    },
    {
      name: "magicLinkTokenExpiry",
      type: "date",
      hidden: true
    },
    // Subscription status
    {
      name: "subscriptionStatus",
      type: "select",
      options: [
        { label: "Active", value: "active" },
        { label: "Unsubscribed", value: "unsubscribed" },
        { label: "Pending", value: "pending" }
      ],
      defaultValue: "pending",
      required: true,
      admin: {
        description: "Current subscription status"
      }
    },
    {
      name: "unsubscribedAt",
      type: "date",
      admin: {
        condition: (data) => data?.subscriptionStatus === "unsubscribed",
        description: "When the user unsubscribed",
        readOnly: true
      }
    },
    // Email preferences
    {
      name: "emailPreferences",
      type: "group",
      fields: [
        {
          name: "newsletter",
          type: "checkbox",
          defaultValue: true,
          label: "Newsletter",
          admin: {
            description: "Receive regular newsletter updates"
          }
        },
        {
          name: "announcements",
          type: "checkbox",
          defaultValue: true,
          label: "Announcements",
          admin: {
            description: "Receive important announcements"
          }
        }
      ],
      admin: {
        description: "Email communication preferences"
      }
    },
    // Source tracking
    {
      name: "source",
      type: "text",
      admin: {
        description: "Where the subscriber signed up from"
      }
    }
  ];
  if (pluginConfig.features?.utmTracking?.enabled) {
    const utmFields = pluginConfig.features.utmTracking.fields || [
      "source",
      "medium",
      "campaign",
      "content",
      "term"
    ];
    defaultFields.push({
      name: "utmParameters",
      type: "group",
      fields: utmFields.map((field) => ({
        name: field,
        type: "text",
        admin: {
          description: `UTM ${field} parameter`
        }
      })),
      admin: {
        description: "UTM tracking parameters"
      }
    });
  }
  defaultFields.push({
    name: "signupMetadata",
    type: "group",
    fields: [
      {
        name: "ipAddress",
        type: "text",
        admin: {
          readOnly: true
        }
      },
      {
        name: "userAgent",
        type: "text",
        admin: {
          readOnly: true
        }
      },
      {
        name: "referrer",
        type: "text",
        admin: {
          readOnly: true
        }
      },
      {
        name: "signupPage",
        type: "text",
        admin: {
          readOnly: true
        }
      }
    ],
    admin: {
      description: "Technical information about signup"
    }
  });
  if (pluginConfig.features?.leadMagnets?.enabled) {
    defaultFields.push({
      name: "leadMagnet",
      type: "relationship",
      relationTo: pluginConfig.features.leadMagnets.collection || "media",
      admin: {
        description: "Lead magnet downloaded at signup"
      }
    });
  }
  let fields = defaultFields;
  if (pluginConfig.fields?.overrides) {
    fields = pluginConfig.fields.overrides({ defaultFields });
  }
  if (pluginConfig.fields?.additional) {
    fields = [...fields, ...pluginConfig.fields.additional];
  }
  const subscribersCollection = {
    slug,
    labels: {
      singular: "Subscriber",
      plural: "Subscribers"
    },
    admin: {
      useAsTitle: "email",
      defaultColumns: ["email", "name", "subscriptionStatus", "createdAt"],
      group: "Newsletter"
    },
    fields,
    hooks: {
      afterChange: [
        async ({ doc, req, operation, previousDoc }) => {
          if (operation === "create") {
            const emailService = req.payload.newsletterEmailService;
            if (emailService) {
              try {
                await emailService.addContact(doc);
              } catch {
              }
            }
            if (doc.subscriptionStatus === "active" && emailService) {
              try {
                const settings = await req.payload.findGlobal({
                  slug: pluginConfig.settingsSlug || "newsletter-settings"
                });
                const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
                const html = await renderEmail("welcome", {
                  email: doc.email,
                  siteName: settings?.brandSettings?.siteName || "Newsletter",
                  preferencesUrl: `${serverURL}/account/preferences`
                  // This could be customized
                });
                await emailService.send({
                  to: doc.email,
                  subject: settings?.brandSettings?.siteName ? `Welcome to ${settings.brandSettings.siteName}!` : "Welcome!",
                  html
                });
                console.warn(`Welcome email sent to: ${doc.email}`);
              } catch (error) {
                console.error("Failed to send welcome email:", error);
              }
            }
            if (pluginConfig.hooks?.afterSubscribe) {
              await pluginConfig.hooks.afterSubscribe({ doc, req });
            }
          }
          if (operation === "update" && previousDoc) {
            const emailService = req.payload.newsletterEmailService;
            if (doc.subscriptionStatus !== previousDoc.subscriptionStatus && emailService) {
              try {
                await emailService.updateContact(doc);
              } catch {
              }
            }
            if (doc.subscriptionStatus === "unsubscribed" && previousDoc.subscriptionStatus !== "unsubscribed") {
              doc.unsubscribedAt = (/* @__PURE__ */ new Date()).toISOString();
              if (pluginConfig.hooks?.afterUnsubscribe) {
                await pluginConfig.hooks.afterUnsubscribe({ doc, req });
              }
            }
          }
        }
      ],
      beforeDelete: [
        async ({ id, req }) => {
          const emailService = req.payload.newsletterEmailService;
          if (emailService) {
            try {
              const doc = await req.payload.findByID({
                collection: slug,
                id
              });
              await emailService.removeContact(doc.email);
            } catch {
            }
          }
        }
      ]
    },
    access: {
      create: () => true,
      // Public can subscribe
      read: adminOrSelf(pluginConfig),
      update: adminOrSelf(pluginConfig),
      delete: adminOnly(pluginConfig)
    },
    timestamps: true
  };
  return subscribersCollection;
};

// src/globals/NewsletterSettings.ts
var createNewsletterSettingsGlobal = (pluginConfig) => {
  const slug = pluginConfig.settingsSlug || "newsletter-settings";
  return {
    slug,
    label: "Newsletter Settings",
    admin: {
      group: "Newsletter",
      description: "Configure email provider settings and templates"
    },
    fields: [
      {
        type: "tabs",
        tabs: [
          {
            label: "Provider Settings",
            fields: [
              {
                name: "provider",
                type: "select",
                label: "Email Provider",
                required: true,
                options: [
                  { label: "Resend", value: "resend" },
                  { label: "Broadcast (Self-Hosted)", value: "broadcast" }
                ],
                defaultValue: pluginConfig.providers.default,
                admin: {
                  description: "Choose which email service to use"
                }
              },
              {
                name: "resendSettings",
                type: "group",
                label: "Resend Settings",
                admin: {
                  condition: (data) => data?.provider === "resend"
                },
                fields: [
                  {
                    name: "apiKey",
                    type: "text",
                    label: "API Key",
                    required: true,
                    admin: {
                      description: "Your Resend API key"
                    }
                  },
                  {
                    name: "audienceIds",
                    type: "array",
                    label: "Audience IDs by Locale",
                    fields: [
                      {
                        name: "locale",
                        type: "select",
                        label: "Locale",
                        required: true,
                        options: pluginConfig.i18n?.locales?.map((locale) => ({
                          label: locale.toUpperCase(),
                          value: locale
                        })) || [
                          { label: "EN", value: "en" }
                        ]
                      },
                      {
                        name: "production",
                        type: "text",
                        label: "Production Audience ID"
                      },
                      {
                        name: "development",
                        type: "text",
                        label: "Development Audience ID"
                      }
                    ]
                  }
                ]
              },
              {
                name: "broadcastSettings",
                type: "group",
                label: "Broadcast Settings",
                admin: {
                  condition: (data) => data?.provider === "broadcast"
                },
                fields: [
                  {
                    name: "apiUrl",
                    type: "text",
                    label: "API URL",
                    required: true,
                    admin: {
                      description: "Your Broadcast instance URL"
                    }
                  },
                  {
                    name: "productionToken",
                    type: "text",
                    label: "Production Token",
                    admin: {
                      description: "Token for production environment"
                    }
                  },
                  {
                    name: "developmentToken",
                    type: "text",
                    label: "Development Token",
                    admin: {
                      description: "Token for development environment"
                    }
                  }
                ]
              },
              {
                name: "fromAddress",
                type: "email",
                label: "From Address",
                required: true,
                admin: {
                  description: "Default sender email address"
                }
              },
              {
                name: "fromName",
                type: "text",
                label: "From Name",
                required: true,
                admin: {
                  description: "Default sender name"
                }
              },
              {
                name: "replyTo",
                type: "email",
                label: "Reply-To Address",
                admin: {
                  description: "Optional reply-to email address"
                }
              }
            ]
          },
          {
            label: "Email Templates",
            fields: [
              {
                name: "emailTemplates",
                type: "group",
                label: "Email Templates",
                fields: [
                  {
                    name: "welcome",
                    type: "group",
                    label: "Welcome Email",
                    fields: [
                      {
                        name: "enabled",
                        type: "checkbox",
                        label: "Send Welcome Email",
                        defaultValue: true
                      },
                      {
                        name: "subject",
                        type: "text",
                        label: "Subject Line",
                        defaultValue: "Welcome to {{fromName}}!",
                        admin: {
                          condition: (data) => data?.emailTemplates?.welcome?.enabled
                        }
                      },
                      {
                        name: "preheader",
                        type: "text",
                        label: "Preheader Text",
                        admin: {
                          condition: (data) => data?.emailTemplates?.welcome?.enabled
                        }
                      }
                    ]
                  },
                  {
                    name: "magicLink",
                    type: "group",
                    label: "Magic Link Email",
                    fields: [
                      {
                        name: "subject",
                        type: "text",
                        label: "Subject Line",
                        defaultValue: "Sign in to {{fromName}}"
                      },
                      {
                        name: "preheader",
                        type: "text",
                        label: "Preheader Text",
                        defaultValue: "Click the link to access your preferences"
                      },
                      {
                        name: "expirationTime",
                        type: "select",
                        label: "Link Expiration",
                        defaultValue: "7d",
                        options: [
                          { label: "1 hour", value: "1h" },
                          { label: "24 hours", value: "24h" },
                          { label: "7 days", value: "7d" },
                          { label: "30 days", value: "30d" }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            label: "Subscription Settings",
            fields: [
              {
                name: "subscriptionSettings",
                type: "group",
                label: "Subscription Settings",
                fields: [
                  {
                    name: "requireDoubleOptIn",
                    type: "checkbox",
                    label: "Require Double Opt-In",
                    defaultValue: false,
                    admin: {
                      description: "Require email confirmation before activating subscriptions"
                    }
                  },
                  {
                    name: "allowedDomains",
                    type: "array",
                    label: "Allowed Email Domains",
                    admin: {
                      description: "Leave empty to allow all domains"
                    },
                    fields: [
                      {
                        name: "domain",
                        type: "text",
                        label: "Domain",
                        required: true,
                        admin: {
                          placeholder: "example.com"
                        }
                      }
                    ]
                  },
                  {
                    name: "maxSubscribersPerIP",
                    type: "number",
                    label: "Max Subscribers per IP",
                    defaultValue: 10,
                    min: 1,
                    admin: {
                      description: "Maximum number of subscriptions allowed from a single IP address"
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
    ],
    hooks: {
      beforeChange: [
        async ({ data, req }) => {
          if (!req.user || req.user.collection !== "users") {
            throw new Error("Only administrators can modify newsletter settings");
          }
          return data;
        }
      ],
      afterChange: [
        async ({ doc, req }) => {
          if (req.payload.newsletterEmailService) {
            try {
              console.warn("Newsletter settings updated, reinitializing service...");
            } catch {
            }
          }
          return doc;
        }
      ]
    },
    access: {
      read: () => true,
      // Settings can be read publicly for validation
      update: adminOnly(pluginConfig)
    }
  };
};

// src/providers/resend.ts
var import_resend = require("resend");

// src/providers/types.ts
var EmailProviderError = class extends Error {
  constructor(message, provider, originalError) {
    super(message);
    this.name = "EmailProviderError";
    this.provider = provider;
    this.originalError = originalError;
  }
};

// src/providers/resend.ts
var ResendProvider = class {
  constructor(config) {
    this.client = new import_resend.Resend(config.apiKey);
    this.audienceIds = config.audienceIds || {};
    this.fromAddress = config.fromAddress;
    this.fromName = config.fromName;
    this.isDevelopment = process.env.NODE_ENV !== "production";
  }
  getProvider() {
    return "resend";
  }
  async send(params) {
    try {
      const from = params.from || {
        email: this.fromAddress,
        name: this.fromName
      };
      if (!params.html && !params.text) {
        throw new Error("Either html or text content is required");
      }
      await this.client.emails.send({
        from: `${from.name} <${from.email}>`,
        to: Array.isArray(params.to) ? params.to : [params.to],
        subject: params.subject,
        html: params.html || "",
        text: params.text,
        replyTo: params.replyTo
      });
    } catch (error) {
      throw new EmailProviderError(
        `Failed to send email via Resend: ${error instanceof Error ? error.message : "Unknown error"}`,
        "resend",
        error
      );
    }
  }
  async addContact(contact) {
    try {
      const audienceId = this.getAudienceId(contact.locale);
      if (!audienceId) {
        console.warn(`No audience ID configured for locale: ${contact.locale}`);
        return;
      }
      await this.client.contacts.create({
        email: contact.email,
        firstName: contact.name?.split(" ")[0],
        lastName: contact.name?.split(" ").slice(1).join(" "),
        unsubscribed: contact.subscriptionStatus === "unsubscribed",
        audienceId
      });
    } catch (error) {
      throw new EmailProviderError(
        `Failed to add contact to Resend: ${error instanceof Error ? error.message : "Unknown error"}`,
        "resend",
        error
      );
    }
  }
  async updateContact(contact) {
    try {
      const audienceId = this.getAudienceId(contact.locale);
      if (!audienceId) {
        console.warn(`No audience ID configured for locale: ${contact.locale}`);
        return;
      }
      const contacts = await this.client.contacts.list({ audienceId });
      const existingContact = contacts.data?.data?.find((c) => c.email === contact.email);
      if (existingContact) {
        await this.client.contacts.update({
          id: existingContact.id,
          audienceId,
          firstName: contact.name?.split(" ")[0],
          lastName: contact.name?.split(" ").slice(1).join(" "),
          unsubscribed: contact.subscriptionStatus === "unsubscribed"
        });
      } else {
        await this.addContact(contact);
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to update contact in Resend: ${error instanceof Error ? error.message : "Unknown error"}`,
        "resend",
        error
      );
    }
  }
  async removeContact(email) {
    try {
      for (const locale in this.audienceIds) {
        const audienceId = this.getAudienceId(locale);
        if (!audienceId) continue;
        const contacts = await this.client.contacts.list({ audienceId });
        const contact = contacts.data?.data?.find((c) => c.email === email);
        if (contact) {
          await this.client.contacts.update({
            id: contact.id,
            audienceId,
            unsubscribed: true
          });
          break;
        }
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to remove contact from Resend: ${error instanceof Error ? error.message : "Unknown error"}`,
        "resend",
        error
      );
    }
  }
  getAudienceId(locale) {
    const localeKey = locale || "en";
    if (!this.audienceIds) return void 0;
    const localeConfig = this.audienceIds[localeKey];
    if (!localeConfig) return void 0;
    const audienceId = this.isDevelopment ? localeConfig.development || localeConfig.production : localeConfig.production || localeConfig.development;
    return audienceId;
  }
};

// src/providers/broadcast.ts
var BroadcastProvider = class {
  constructor(config) {
    this.apiUrl = config.apiUrl.replace(/\/$/, "");
    this.isDevelopment = process.env.NODE_ENV !== "production";
    this.token = this.isDevelopment ? config.tokens.development || config.tokens.production || "" : config.tokens.production || config.tokens.development || "";
    this.fromAddress = config.fromAddress;
    this.fromName = config.fromName;
  }
  getProvider() {
    return "broadcast";
  }
  async send(params) {
    try {
      const from = params.from || {
        email: this.fromAddress,
        name: this.fromName
      };
      const recipients = Array.isArray(params.to) ? params.to : [params.to];
      const response = await fetch(`${this.apiUrl}/api/v1/emails`, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${this.token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          from_email: from.email,
          from_name: from.name,
          to: recipients,
          subject: params.subject,
          html_body: params.html,
          text_body: params.text,
          reply_to: params.replyTo
        })
      });
      if (!response.ok) {
        const error = await response.text();
        throw new Error(`Broadcast API error: ${response.status} - ${error}`);
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to send email via Broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
        "broadcast",
        error
      );
    }
  }
  async addContact(contact) {
    try {
      const [firstName, ...lastNameParts] = (contact.name || "").split(" ");
      const lastName = lastNameParts.join(" ");
      const response = await fetch(`${this.apiUrl}/api/v1/subscribers.json`, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${this.token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          subscriber: {
            email: contact.email,
            first_name: firstName || void 0,
            last_name: lastName || void 0,
            tags: [`lang:${contact.locale || "en"}`],
            is_active: contact.subscriptionStatus === "active",
            source: contact.source
          }
        })
      });
      if (!response.ok) {
        const error = await response.text();
        throw new Error(`Broadcast API error: ${response.status} - ${error}`);
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to add contact to Broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
        "broadcast",
        error
      );
    }
  }
  async updateContact(contact) {
    try {
      const searchResponse = await fetch(
        `${this.apiUrl}/api/v1/subscribers/find.json?email=${encodeURIComponent(contact.email)}`,
        {
          headers: {
            "Authorization": `Bearer ${this.token}`
          }
        }
      );
      if (!searchResponse.ok) {
        await this.addContact(contact);
        return;
      }
      const existingContact = await searchResponse.json();
      if (!existingContact || !existingContact.id) {
        await this.addContact(contact);
        return;
      }
      const [firstName, ...lastNameParts] = (contact.name || "").split(" ");
      const lastName = lastNameParts.join(" ");
      const response = await fetch(`${this.apiUrl}/api/v1/subscribers.json`, {
        method: "PATCH",
        headers: {
          "Authorization": `Bearer ${this.token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          email: contact.email,
          subscriber: {
            first_name: firstName || void 0,
            last_name: lastName || void 0,
            tags: [`lang:${contact.locale || "en"}`],
            is_active: contact.subscriptionStatus === "active",
            source: contact.source
          }
        })
      });
      if (!response.ok) {
        const error = await response.text();
        throw new Error(`Broadcast API error: ${response.status} - ${error}`);
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to update contact in Broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
        "broadcast",
        error
      );
    }
  }
  async removeContact(email) {
    try {
      const searchResponse = await fetch(
        `${this.apiUrl}/api/v1/subscribers/find.json?email=${encodeURIComponent(email)}`,
        {
          headers: {
            "Authorization": `Bearer ${this.token}`
          }
        }
      );
      if (!searchResponse.ok) {
        return;
      }
      const contact = await searchResponse.json();
      if (!contact || !contact.id) {
        return;
      }
      const response = await fetch(`${this.apiUrl}/api/v1/subscribers/deactivate.json`, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${this.token}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ email })
      });
      if (!response.ok) {
        const error = await response.text();
        throw new Error(`Broadcast API error: ${response.status} - ${error}`);
      }
    } catch (error) {
      throw new EmailProviderError(
        `Failed to remove contact from Broadcast: ${error instanceof Error ? error.message : "Unknown error"}`,
        "broadcast",
        error
      );
    }
  }
};

// src/providers/index.ts
var EmailService = class {
  constructor(config) {
    this.provider = this.createProvider(config);
  }
  createProvider(config) {
    const baseConfig = {
      fromAddress: config.fromAddress,
      fromName: config.fromName
    };
    switch (config.provider) {
      case "resend":
        if (!config.resend) {
          throw new Error("Resend configuration is required when using Resend provider");
        }
        return new ResendProvider({
          ...config.resend,
          ...baseConfig
        });
      case "broadcast":
        if (!config.broadcast) {
          throw new Error("Broadcast configuration is required when using Broadcast provider");
        }
        return new BroadcastProvider({
          ...config.broadcast,
          ...baseConfig
        });
      default:
        throw new Error(`Unknown email provider: ${config.provider}`);
    }
  }
  async send(params) {
    return this.provider.send(params);
  }
  async addContact(contact) {
    return this.provider.addContact(contact);
  }
  async updateContact(contact) {
    return this.provider.updateContact(contact);
  }
  async removeContact(email) {
    return this.provider.removeContact(email);
  }
  getProvider() {
    return this.provider.getProvider();
  }
  /**
   * Update the provider configuration
   * Useful when settings are changed in the admin UI
   */
  updateConfig(config) {
    this.provider = this.createProvider(config);
  }
};
function createEmailService(config) {
  return new EmailService(config);
}

// src/utils/validation.ts
var import_isomorphic_dompurify = __toESM(require("isomorphic-dompurify"), 1);
function isValidEmail(email) {
  if (!email || typeof email !== "string") return false;
  const trimmed = email.trim();
  if (trimmed.length > 255) return false;
  if (trimmed.includes("<") || trimmed.includes(">")) return false;
  if (trimmed.includes("javascript:")) return false;
  if (trimmed.includes("data:")) return false;
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  if (!emailRegex.test(trimmed)) return false;
  const parts = trimmed.split("@");
  if (parts.length !== 2) return false;
  const [localPart, domain] = parts;
  if (localPart.length > 64 || localPart.length === 0) return false;
  if (localPart.startsWith(".") || localPart.endsWith(".")) return false;
  if (domain.startsWith(".") || domain.endsWith(".")) return false;
  if (domain.includes("..")) return false;
  if (localPart.includes("..")) return false;
  return true;
}
function isDomainAllowed(email, allowedDomains) {
  if (!isValidEmail(email)) {
    return false;
  }
  if (!allowedDomains || allowedDomains.length === 0) {
    return true;
  }
  const domain = email.split("@")[1]?.toLowerCase();
  if (!domain) return false;
  return allowedDomains.some(
    (allowedDomain) => domain === allowedDomain.toLowerCase()
  );
}
function sanitizeInput(input) {
  if (!input) return "";
  let cleaned = import_isomorphic_dompurify.default.sanitize(input, {
    ALLOWED_TAGS: [],
    ALLOWED_ATTR: [],
    KEEP_CONTENT: true
  });
  cleaned = cleaned.replace(/javascript:/gi, "").replace(/data:/gi, "").replace(/vbscript:/gi, "").replace(/file:\/\//gi, "").replace(/onload/gi, "").replace(/onerror/gi, "").replace(/onclick/gi, "").replace(/onmouseover/gi, "").replace(/alert\(/gi, "").replace(/prompt\(/gi, "").replace(/confirm\(/gi, "").replace(/\|/g, "").replace(/;/g, "").replace(/`/g, "").replace(/&&/g, "").replace(/\$\(/g, "").replace(/\.\./g, "").replace(/\/..\//g, "").replace(/\0/g, "");
  return cleaned.trim();
}
function extractUTMParams(searchParams) {
  const utmParams = {};
  const utmKeys = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"];
  utmKeys.forEach((key) => {
    const value = searchParams.get(key);
    if (value) {
      const shortKey = key.replace("utm_", "");
      utmParams[shortKey] = value;
    }
  });
  return utmParams;
}
function isValidSource(source) {
  if (!source || typeof source !== "string") return false;
  const allowedSources = [
    "website",
    "api",
    "import",
    "admin",
    "signup-form",
    "magic-link",
    "preferences",
    "external"
  ];
  return allowedSources.includes(source);
}
function validateSubscriberData(data) {
  const errors = [];
  if (!data.email) {
    errors.push("Email is required");
  } else if (!isValidEmail(data.email)) {
    errors.push("Invalid email format");
  }
  if (data.name && data.name.length > 100) {
    errors.push("Name is too long (max 100 characters)");
  }
  if (data.source !== void 0) {
    if (!data.source || data.source.length === 0) {
      errors.push("Source cannot be empty");
    } else if (data.source.length > 50) {
      errors.push("Source is too long (max 50 characters)");
    } else if (!isValidSource(data.source)) {
      errors.push("Invalid source value");
    }
  }
  return {
    valid: errors.length === 0,
    errors
  };
}

// src/utils/jwt.ts
var import_jsonwebtoken = __toESM(require("jsonwebtoken"), 1);
function getJWTSecret() {
  const secret = process.env.JWT_SECRET || process.env.PAYLOAD_SECRET;
  if (!secret) {
    console.warn(
      "WARNING: No JWT_SECRET or PAYLOAD_SECRET found in environment variables. Magic link authentication will not work properly. Please set JWT_SECRET in your environment."
    );
    return "INSECURE_DEVELOPMENT_SECRET_PLEASE_SET_JWT_SECRET";
  }
  return secret;
}
function generateMagicLinkToken(subscriberId, email, config) {
  const payload = {
    subscriberId,
    email,
    type: "magic-link"
  };
  const expiresIn = config.auth?.tokenExpiration || "7d";
  return import_jsonwebtoken.default.sign(payload, getJWTSecret(), {
    expiresIn,
    issuer: "payload-newsletter-plugin"
  });
}
function verifyMagicLinkToken(token) {
  try {
    const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
      issuer: "payload-newsletter-plugin"
    });
    if (payload.type !== "magic-link") {
      throw new Error("Invalid token type");
    }
    return payload;
  } catch (error) {
    if (error instanceof Error && error.name === "TokenExpiredError") {
      throw new Error("Magic link has expired. Please request a new one.");
    }
    if (error instanceof Error && error.name === "JsonWebTokenError") {
      throw new Error("Invalid magic link token");
    }
    throw error;
  }
}
function generateSessionToken(subscriberId, email) {
  const payload = {
    subscriberId,
    email,
    type: "session"
  };
  return import_jsonwebtoken.default.sign(payload, getJWTSecret(), {
    expiresIn: "30d",
    issuer: "payload-newsletter-plugin"
  });
}
function verifySessionToken(token) {
  try {
    const payload = import_jsonwebtoken.default.verify(token, getJWTSecret(), {
      issuer: "payload-newsletter-plugin"
    });
    if (payload.type !== "session") {
      throw new Error("Invalid token type");
    }
    return payload;
  } catch (error) {
    if (error instanceof Error && error.name === "TokenExpiredError") {
      throw new Error("Session has expired. Please sign in again.");
    }
    if (error instanceof Error && error.name === "JsonWebTokenError") {
      throw new Error("Invalid session token");
    }
    throw error;
  }
}
function generateMagicLinkURL(token, baseURL, config) {
  const path = config.auth?.magicLinkPath || "/newsletter/verify";
  const url = new URL(path, baseURL);
  url.searchParams.set("token", token);
  return url.toString();
}

// src/endpoints/subscribe.ts
var createSubscribeEndpoint = (config) => {
  return {
    path: "/newsletter/subscribe",
    method: "post",
    handler: async (req) => {
      try {
        const data = await req.json();
        const {
          email,
          name,
          source,
          preferences,
          leadMagnet,
          surveyResponses,
          metadata = {}
        } = data;
        const trimmedEmail = email?.trim();
        const validation = validateSubscriberData({ email: trimmedEmail, name, source });
        if (!validation.valid) {
          return Response.json({
            success: false,
            errors: validation.errors
          }, { status: 400 });
        }
        const settings = await req.payload.findGlobal({
          slug: config.settingsSlug || "newsletter-settings",
          overrideAccess: false
          // No user context for public endpoint
        });
        const allowedDomains = settings?.subscriptionSettings?.allowedDomains?.map((d) => d.domain) || [];
        if (!isDomainAllowed(trimmedEmail, allowedDomains)) {
          return Response.json({
            success: false,
            error: "Email domain not allowed"
          }, { status: 400 });
        }
        const existing = await req.payload.find({
          collection: config.subscribersSlug || "subscribers",
          where: {
            email: {
              equals: trimmedEmail.toLowerCase()
            }
          },
          overrideAccess: true
          // Need to check for duplicates in public endpoint
        });
        if (existing.docs.length > 0) {
          const subscriber2 = existing.docs[0];
          if (subscriber2.subscriptionStatus === "unsubscribed") {
            return Response.json({
              success: false,
              error: "This email has been unsubscribed. Please contact support to resubscribe."
            }, { status: 400 });
          }
          return Response.json({
            success: false,
            error: "Already subscribed",
            subscriber: {
              id: subscriber2.id,
              email: subscriber2.email,
              subscriptionStatus: subscriber2.subscriptionStatus
            }
          }, { status: 400 });
        }
        const ipAddress = req.ip || req.connection?.remoteAddress;
        const maxPerIP = settings?.subscriptionSettings?.maxSubscribersPerIP || 10;
        const ipSubscribers = await req.payload.find({
          collection: config.subscribersSlug || "subscribers",
          where: {
            "signupMetadata.ipAddress": {
              equals: ipAddress
            }
          },
          overrideAccess: true
          // Need to check IP limits in public endpoint
        });
        if (ipSubscribers.docs.length >= maxPerIP) {
          return Response.json({
            success: false,
            error: "Too many subscriptions from this IP address"
          }, { status: 429 });
        }
        const referer = req.headers.get("referer") || req.headers.get("referrer") || "";
        let utmParams = {};
        if (referer) {
          try {
            utmParams = extractUTMParams(new URL(referer).searchParams);
          } catch {
          }
        }
        const subscriberData = {
          email: trimmedEmail.toLowerCase(),
          name: name ? sanitizeInput(name) : void 0,
          locale: metadata.locale || config.i18n?.defaultLocale || "en",
          subscriptionStatus: settings?.subscriptionSettings?.requireDoubleOptIn ? "pending" : "active",
          source: source || "api",
          emailPreferences: {
            newsletter: true,
            announcements: true,
            ...preferences || {}
          },
          signupMetadata: {
            ipAddress,
            userAgent: req.headers.get("user-agent") || void 0,
            referrer: referer,
            signupPage: metadata.signupPage || referer
          }
        };
        if (config.features?.utmTracking?.enabled && Object.keys(utmParams).length > 0) {
          subscriberData.utmParameters = utmParams;
        }
        if (config.features?.leadMagnets?.enabled && leadMagnet) {
          subscriberData.leadMagnet = leadMagnet;
        }
        const subscriber = await req.payload.create({
          collection: config.subscribersSlug || "subscribers",
          data: subscriberData,
          overrideAccess: true
          // Public endpoint needs to create subscribers
        });
        if (config.features?.surveys?.enabled && surveyResponses) {
        }
        if (settings?.subscriptionSettings?.requireDoubleOptIn) {
          try {
            const token = generateMagicLinkToken(
              String(subscriber.id),
              subscriber.email,
              config
            );
            const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
            const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
            const emailService = req.payload.newsletterEmailService;
            if (emailService) {
              const html = await renderEmail("magic-link", {
                magicLink: magicLinkURL,
                email: subscriber.email,
                siteName: settings?.brandSettings?.siteName || "Newsletter",
                expiresIn: config.auth?.tokenExpiration || "7d"
              });
              await emailService.send({
                to: subscriber.email,
                subject: settings?.brandSettings?.siteName ? `Verify your email for ${settings.brandSettings.siteName}` : "Verify your email",
                html
              });
            } else {
              console.warn("Email service not initialized, cannot send magic link");
            }
          } catch (error) {
            console.error("Failed to send magic link email:", error);
          }
        }
        return Response.json({
          success: true,
          subscriber: {
            id: subscriber.id,
            email: subscriber.email,
            subscriptionStatus: subscriber.subscriptionStatus
          },
          message: settings?.subscriptionSettings?.requireDoubleOptIn ? "Please check your email to confirm your subscription" : "Successfully subscribed"
        });
      } catch {
        return Response.json({
          success: false,
          error: "Failed to subscribe. Please try again."
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/verify-magic-link.ts
var createVerifyMagicLinkEndpoint = (config) => {
  return {
    path: "/newsletter/verify-magic-link",
    method: "post",
    handler: async (req) => {
      try {
        const data = await req.json();
        const { token } = data;
        if (!token) {
          return Response.json({
            success: false,
            error: "Token is required"
          }, { status: 400 });
        }
        let payload;
        try {
          payload = verifyMagicLinkToken(token);
        } catch (error) {
          return Response.json({
            success: false,
            error: error instanceof Error ? error.message : "Invalid token"
          }, { status: 401 });
        }
        const subscriber = await req.payload.findByID({
          collection: config.subscribersSlug || "subscribers",
          id: payload.subscriberId
          // Keep overrideAccess: true for token verification
        });
        if (!subscriber) {
          return Response.json({
            success: false,
            error: "Subscriber not found"
          }, { status: 404 });
        }
        if (subscriber.email !== payload.email) {
          return Response.json({
            success: false,
            error: "Invalid token"
          }, { status: 401 });
        }
        if (subscriber.subscriptionStatus === "unsubscribed") {
          return Response.json({
            success: false,
            error: "This email has been unsubscribed"
          }, { status: 403 });
        }
        const syntheticUser = {
          collection: "subscribers",
          id: subscriber.id,
          email: subscriber.email
        };
        let isNewlyActivated = false;
        if (subscriber.subscriptionStatus === "pending") {
          await req.payload.update({
            collection: config.subscribersSlug || "subscribers",
            id: subscriber.id,
            data: {
              subscriptionStatus: "active"
            },
            overrideAccess: false,
            user: syntheticUser
          });
          isNewlyActivated = true;
        }
        await req.payload.update({
          collection: config.subscribersSlug || "subscribers",
          id: subscriber.id,
          data: {
            magicLinkToken: null,
            magicLinkTokenExpiry: null
          },
          overrideAccess: false,
          user: syntheticUser
        });
        const sessionToken = generateSessionToken(
          String(subscriber.id),
          subscriber.email
        );
        if (isNewlyActivated) {
          try {
            const emailService = req.payload.newsletterEmailService;
            if (emailService) {
              const settings = await req.payload.findGlobal({
                slug: config.settingsSlug || "newsletter-settings"
              });
              const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
              const html = await renderEmail("welcome", {
                email: subscriber.email,
                siteName: settings?.brandSettings?.siteName || "Newsletter",
                preferencesUrl: `${serverURL}/account/preferences`
                // This could be customized
              });
              await emailService.send({
                to: subscriber.email,
                subject: settings?.brandSettings?.siteName ? `Welcome to ${settings.brandSettings.siteName}!` : "Welcome!",
                html
              });
            } else {
              console.warn("Email service not initialized, cannot send welcome email");
            }
          } catch (error) {
            console.error("Failed to send welcome email:", error);
          }
        }
        const headers = new Headers();
        headers.append("Set-Cookie", `newsletter-auth=${sessionToken}; HttpOnly; Secure=${process.env.NODE_ENV === "production"}; SameSite=Lax; Path=/; Max-Age=${30 * 24 * 60 * 60}`);
        return Response.json({
          success: true,
          sessionToken,
          subscriber: {
            id: subscriber.id,
            email: subscriber.email,
            name: subscriber.name,
            locale: subscriber.locale,
            emailPreferences: subscriber.emailPreferences
          }
        }, { headers });
      } catch (error) {
        console.error("Verify magic link error:", error);
        return Response.json({
          success: false,
          error: "Failed to verify magic link"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/preferences.ts
var createPreferencesEndpoint = (config) => {
  return {
    path: "/newsletter/preferences",
    method: "get",
    handler: async (req) => {
      try {
        const authHeader = req.headers.get("authorization");
        if (!authHeader || !authHeader.startsWith("Bearer ")) {
          return Response.json({
            success: false,
            error: "Authorization required"
          }, { status: 401 });
        }
        const token = authHeader.substring(7);
        let payload;
        try {
          payload = verifySessionToken(token);
        } catch (error) {
          return Response.json({
            success: false,
            error: error instanceof Error ? error.message : "Invalid token"
          }, { status: 401 });
        }
        const subscriber = await req.payload.findByID({
          collection: config.subscribersSlug || "subscribers",
          id: payload.subscriberId,
          overrideAccess: false,
          user: {
            collection: "subscribers",
            id: payload.subscriberId,
            email: payload.email
          }
        });
        if (!subscriber) {
          return Response.json({
            success: false,
            error: "Subscriber not found"
          }, { status: 404 });
        }
        return Response.json({
          success: true,
          subscriber: {
            id: subscriber.id,
            email: subscriber.email,
            name: subscriber.name,
            locale: subscriber.locale,
            emailPreferences: subscriber.emailPreferences,
            subscriptionStatus: subscriber.subscriptionStatus
          }
        });
      } catch (error) {
        console.error("Get preferences error:", error);
        return Response.json({
          success: false,
          error: "Failed to get preferences"
        }, { status: 500 });
      }
    }
  };
};
var createUpdatePreferencesEndpoint = (config) => {
  return {
    path: "/newsletter/preferences",
    method: "post",
    handler: async (req) => {
      try {
        const authHeader = req.headers.get("authorization");
        if (!authHeader || !authHeader.startsWith("Bearer ")) {
          return Response.json({
            success: false,
            error: "Authorization required"
          }, { status: 401 });
        }
        const token = authHeader.substring(7);
        let payload;
        try {
          payload = verifySessionToken(token);
        } catch (error) {
          return Response.json({
            success: false,
            error: error instanceof Error ? error.message : "Invalid token"
          }, { status: 401 });
        }
        const data = await req.json();
        const { name, locale, emailPreferences } = data;
        const updateData = {};
        if (name !== void 0) {
          updateData.name = name;
        }
        if (locale !== void 0) {
          updateData.locale = locale;
        }
        if (emailPreferences !== void 0) {
          updateData.emailPreferences = emailPreferences;
        }
        const subscriber = await req.payload.update({
          collection: config.subscribersSlug || "subscribers",
          id: payload.subscriberId,
          data: updateData,
          overrideAccess: false,
          user: {
            collection: "subscribers",
            id: payload.subscriberId,
            email: payload.email
          }
        });
        return Response.json({
          success: true,
          subscriber: {
            id: subscriber.id,
            email: subscriber.email,
            name: subscriber.name,
            locale: subscriber.locale,
            emailPreferences: subscriber.emailPreferences,
            subscriptionStatus: subscriber.subscriptionStatus
          }
        });
      } catch (error) {
        console.error("Update preferences error:", error);
        return Response.json({
          success: false,
          error: "Failed to update preferences"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/unsubscribe.ts
var createUnsubscribeEndpoint = (config) => {
  return {
    path: "/newsletter/unsubscribe",
    method: "post",
    handler: async (req) => {
      try {
        const data = await req.json();
        const { email, token } = data;
        if (!email && !token) {
          return Response.json({
            success: false,
            error: "Email or token is required"
          }, { status: 400 });
        }
        let subscriber;
        if (token) {
          try {
            const jwt3 = await import("jsonwebtoken");
            const payload = jwt3.verify(
              token,
              process.env.JWT_SECRET || process.env.PAYLOAD_SECRET || ""
            );
            if (payload.type !== "unsubscribe") {
              throw new Error("Invalid token type");
            }
            subscriber = await req.payload.findByID({
              collection: config.subscribersSlug || "subscribers",
              id: payload.subscriberId
            });
          } catch {
            return Response.json({
              success: false,
              error: "Invalid or expired unsubscribe link"
            }, { status: 401 });
          }
        } else {
          if (!email || !isValidEmail(email)) {
            return Response.json({
              success: false,
              error: "Invalid email format"
            }, { status: 400 });
          }
          const result = await req.payload.find({
            collection: config.subscribersSlug || "subscribers",
            where: {
              email: {
                equals: email.toLowerCase()
              }
            }
          });
          if (result.docs.length === 0) {
            return Response.json({
              success: true,
              message: "If this email was subscribed, it has been unsubscribed."
            });
          }
          subscriber = result.docs[0];
        }
        if (!subscriber) {
          return Response.json({
            success: true,
            message: "If this email was subscribed, it has been unsubscribed."
          });
        }
        if (subscriber.subscriptionStatus === "unsubscribed") {
          return Response.json({
            success: true,
            message: "Already unsubscribed"
          });
        }
        await req.payload.update({
          collection: config.subscribersSlug || "subscribers",
          id: subscriber.id,
          data: {
            subscriptionStatus: "unsubscribed",
            unsubscribedAt: (/* @__PURE__ */ new Date()).toISOString()
          },
          overrideAccess: false,
          user: {
            collection: "subscribers",
            id: subscriber.id,
            email: subscriber.email
          }
        });
        return Response.json({
          success: true,
          message: "Successfully unsubscribed"
        });
      } catch (error) {
        console.error("Unsubscribe error:", error);
        return Response.json({
          success: false,
          error: "Failed to unsubscribe. Please try again."
        }, { status: 500 });
      }
    }
  };
};

// src/utils/rate-limiter.ts
var RateLimiter = class {
  constructor(options) {
    this.attempts = /* @__PURE__ */ new Map();
    this.options = options;
  }
  async checkLimit(key) {
    const now = Date.now();
    const record = this.attempts.get(key);
    if (!record || record.resetTime < now) {
      this.attempts.set(key, {
        count: 1,
        resetTime: now + this.options.windowMs
      });
      return true;
    }
    if (record.count >= this.options.maxAttempts) {
      return false;
    }
    record.count++;
    return true;
  }
  async incrementAttempt(key) {
    const now = Date.now();
    const record = this.attempts.get(key);
    if (!record || record.resetTime < now) {
      this.attempts.set(key, {
        count: 1,
        resetTime: now + this.options.windowMs
      });
    } else {
      record.count++;
    }
  }
  async reset(key) {
    this.attempts.delete(key);
  }
  async resetAll() {
    this.attempts.clear();
  }
};

// src/endpoints/signin.ts
var signinRateLimiter = new RateLimiter({
  maxAttempts: 5,
  windowMs: 15 * 60 * 1e3,
  // 15 minutes
  prefix: "signin"
});
var createSigninEndpoint = (config) => {
  return {
    path: "/newsletter/signin",
    method: "post",
    handler: async (req) => {
      try {
        const data = await req.json();
        const { email } = data;
        const validation = validateSubscriberData({ email });
        if (!validation.valid) {
          return Response.json({
            success: false,
            errors: validation.errors
          }, { status: 400 });
        }
        const rateLimitKey = `signin:${email.toLowerCase()}`;
        const allowed = await signinRateLimiter.checkLimit(rateLimitKey);
        if (!allowed) {
          return Response.json({
            success: false,
            error: "Too many sign-in attempts. Please try again later."
          }, { status: 429 });
        }
        const result = await req.payload.find({
          collection: config.subscribersSlug || "subscribers",
          where: {
            email: { equals: email.toLowerCase() },
            subscriptionStatus: { equals: "active" }
          },
          limit: 1,
          overrideAccess: true
          // Need to check subscriber exists
        });
        if (result.docs.length === 0) {
          return Response.json({
            success: false,
            error: "Email not found. Please subscribe first."
          }, { status: 404 });
        }
        const subscriber = result.docs[0];
        const token = generateMagicLinkToken(
          String(subscriber.id),
          subscriber.email,
          config
        );
        const serverURL = req.payload.config.serverURL || process.env.PAYLOAD_PUBLIC_SERVER_URL || "";
        const magicLinkURL = generateMagicLinkURL(token, serverURL, config);
        const emailService = req.payload.newsletterEmailService;
        if (emailService) {
          const settings = await req.payload.findGlobal({
            slug: config.settingsSlug || "newsletter-settings"
          });
          const html = await renderEmail("signin", {
            magicLink: magicLinkURL,
            email: subscriber.email,
            siteName: settings?.brandSettings?.siteName || "Newsletter",
            expiresIn: config.auth?.tokenExpiration || "7d"
          });
          await emailService.send({
            to: subscriber.email,
            subject: settings?.brandSettings?.siteName ? `Sign in to ${settings.brandSettings.siteName}` : "Sign in to your account",
            html
          });
        } else {
          console.warn("Email service not initialized, cannot send sign-in link");
        }
        return Response.json({
          success: true,
          message: "Check your email for the sign-in link"
        });
      } catch (error) {
        console.error("Sign-in error:", error);
        return Response.json({
          success: false,
          error: "Failed to process sign-in request"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/me.ts
var createMeEndpoint = (config) => {
  return {
    path: "/newsletter/me",
    method: "get",
    handler: async (req) => {
      try {
        const cookieHeader = req.headers.get("cookie") || "";
        const cookies = Object.fromEntries(
          cookieHeader.split("; ").map((c) => {
            const [key, ...value] = c.split("=");
            return [key, value.join("=")];
          })
        );
        const token = cookies["newsletter-auth"];
        if (!token) {
          return Response.json({
            success: false,
            error: "Not authenticated"
          }, { status: 401 });
        }
        let payload;
        try {
          payload = verifySessionToken(token);
        } catch {
          return Response.json({
            success: false,
            error: "Invalid or expired session"
          }, { status: 401 });
        }
        const subscriber = await req.payload.findByID({
          collection: config.subscribersSlug || "subscribers",
          id: payload.subscriberId,
          overrideAccess: true
          // Need to get subscriber data
        });
        if (!subscriber || subscriber.subscriptionStatus !== "active") {
          return Response.json({
            success: false,
            error: "Not authenticated"
          }, { status: 401 });
        }
        return Response.json({
          success: true,
          subscriber: {
            id: subscriber.id,
            email: subscriber.email,
            name: subscriber.name,
            status: subscriber.subscriptionStatus,
            preferences: {
              frequency: subscriber.emailPreferences?.frequency,
              categories: subscriber.emailPreferences?.categories
            },
            createdAt: subscriber.createdAt,
            updatedAt: subscriber.updatedAt
          }
        });
      } catch (error) {
        console.error("Me endpoint error:", error);
        return Response.json({
          success: false,
          error: "Internal server error"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/signout.ts
var createSignoutEndpoint = (_config) => {
  return {
    path: "/newsletter/signout",
    method: "post",
    handler: (_req) => {
      try {
        const headers = new Headers();
        headers.append("Set-Cookie", `newsletter-auth=; HttpOnly; Secure=${process.env.NODE_ENV === "production"}; SameSite=Lax; Path=/; Max-Age=0`);
        return Response.json({
          success: true,
          message: "Signed out successfully"
        }, { headers });
      } catch (error) {
        console.error("Signout error:", error);
        return Response.json({
          success: false,
          error: "Failed to sign out"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/broadcasts/send.ts
init_types();

// src/utils/auth.ts
async function getAuthenticatedUser(req) {
  try {
    const me = await req.payload.find({
      collection: "users",
      where: {
        id: {
          equals: "me"
          // Special value in Payload to get current user
        }
      },
      limit: 1,
      depth: 0
    });
    return me.docs[0] || null;
  } catch {
    return null;
  }
}
async function requireAdmin(req, config) {
  const user = await getAuthenticatedUser(req);
  if (!user) {
    return {
      authorized: false,
      error: "Authentication required"
    };
  }
  if (!isAdmin(user, config)) {
    return {
      authorized: false,
      error: "Admin access required"
    };
  }
  return {
    authorized: true,
    user
  };
}

// src/endpoints/broadcasts/send.ts
var createSendBroadcastEndpoint = (config, collectionSlug) => {
  return {
    path: `/${collectionSlug}/:id/send`,
    method: "post",
    handler: async (req) => {
      try {
        const auth = await requireAdmin(req, config);
        if (!auth.authorized) {
          return Response.json({
            success: false,
            error: auth.error
          }, { status: 401 });
        }
        const broadcastProvider = req.payload.newsletterProvider;
        if (!broadcastProvider) {
          return Response.json({
            success: false,
            error: "Broadcast management is not enabled"
          }, { status: 400 });
        }
        const url = new URL(req.url || "", `http://localhost`);
        const pathParts = url.pathname.split("/");
        const id = pathParts[pathParts.length - 2];
        if (!id) {
          return Response.json({
            success: false,
            error: "Broadcast ID is required"
          }, { status: 400 });
        }
        const data = await (req.json?.() || Promise.resolve({}));
        const broadcastDoc = await req.payload.findByID({
          collection: collectionSlug,
          id,
          user: auth.user
        });
        if (!broadcastDoc || !broadcastDoc.providerId) {
          return Response.json({
            success: false,
            error: "Broadcast not found or not synced with provider"
          }, { status: 404 });
        }
        const broadcast = await broadcastProvider.send(broadcastDoc.providerId, data);
        await req.payload.update({
          collection: collectionSlug,
          id,
          data: {
            status: "sending" /* SENDING */,
            sentAt: (/* @__PURE__ */ new Date()).toISOString()
          },
          user: auth.user
        });
        return Response.json({
          success: true,
          message: "Broadcast sent successfully",
          broadcast
        });
      } catch (error) {
        console.error("Failed to send broadcast:", error);
        if (error instanceof NewsletterProviderError) {
          return Response.json({
            success: false,
            error: error.message,
            code: error.code
          }, { status: error.code === "NOT_SUPPORTED" ? 501 : 500 });
        }
        return Response.json({
          success: false,
          error: "Failed to send broadcast"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/broadcasts/schedule.ts
init_types();
var createScheduleBroadcastEndpoint = (config, collectionSlug) => {
  return {
    path: `/${collectionSlug}/:id/schedule`,
    method: "post",
    handler: async (req) => {
      try {
        const auth = await requireAdmin(req, config);
        if (!auth.authorized) {
          return Response.json({
            success: false,
            error: auth.error
          }, { status: 401 });
        }
        const broadcastProvider = req.payload.newsletterProvider;
        if (!broadcastProvider) {
          return Response.json({
            success: false,
            error: "Broadcast management is not enabled"
          }, { status: 400 });
        }
        const url = new URL(req.url || "", `http://localhost`);
        const pathParts = url.pathname.split("/");
        const id = pathParts[pathParts.length - 2];
        if (!id) {
          return Response.json({
            success: false,
            error: "Broadcast ID is required"
          }, { status: 400 });
        }
        const data = await (req.json?.() || Promise.resolve({}));
        const { scheduledAt } = data;
        if (!scheduledAt) {
          return Response.json({
            success: false,
            error: "scheduledAt is required"
          }, { status: 400 });
        }
        const scheduledDate = new Date(scheduledAt);
        if (isNaN(scheduledDate.getTime())) {
          return Response.json({
            success: false,
            error: "Invalid scheduledAt date"
          }, { status: 400 });
        }
        if (scheduledDate <= /* @__PURE__ */ new Date()) {
          return Response.json({
            success: false,
            error: "scheduledAt must be in the future"
          }, { status: 400 });
        }
        const broadcastDoc = await req.payload.findByID({
          collection: collectionSlug,
          id,
          user: auth.user
        });
        if (!broadcastDoc || !broadcastDoc.providerId) {
          return Response.json({
            success: false,
            error: "Broadcast not found or not synced with provider"
          }, { status: 404 });
        }
        const broadcast = await broadcastProvider.schedule(broadcastDoc.providerId, scheduledDate);
        await req.payload.update({
          collection: collectionSlug,
          id,
          data: {
            status: "scheduled" /* SCHEDULED */,
            scheduledAt: scheduledDate.toISOString()
          },
          user: auth.user
        });
        return Response.json({
          success: true,
          message: `Broadcast scheduled for ${scheduledDate.toISOString()}`,
          broadcast
        });
      } catch (error) {
        console.error("Failed to schedule broadcast:", error);
        if (error instanceof NewsletterProviderError) {
          return Response.json({
            success: false,
            error: error.message,
            code: error.code
          }, { status: error.code === "NOT_SUPPORTED" ? 501 : 500 });
        }
        return Response.json({
          success: false,
          error: "Failed to schedule broadcast"
        }, { status: 500 });
      }
    }
  };
};

// src/utils/emailSafeHtml.ts
var import_isomorphic_dompurify2 = __toESM(require("isomorphic-dompurify"), 1);
var EMAIL_SAFE_CONFIG = {
  ALLOWED_TAGS: [
    "p",
    "br",
    "strong",
    "b",
    "em",
    "i",
    "u",
    "strike",
    "s",
    "span",
    "a",
    "h1",
    "h2",
    "h3",
    "ul",
    "ol",
    "li",
    "blockquote",
    "hr"
  ],
  ALLOWED_ATTR: ["href", "style", "target", "rel", "align"],
  ALLOWED_STYLES: {
    "*": [
      "color",
      "background-color",
      "font-size",
      "font-weight",
      "font-style",
      "text-decoration",
      "text-align",
      "margin",
      "margin-top",
      "margin-right",
      "margin-bottom",
      "margin-left",
      "padding",
      "padding-top",
      "padding-right",
      "padding-bottom",
      "padding-left",
      "line-height",
      "border-left",
      "border-left-width",
      "border-left-style",
      "border-left-color"
    ]
  },
  FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form", "input"],
  FORBID_ATTR: ["class", "id", "onclick", "onload", "onerror"]
};
async function convertToEmailSafeHtml(editorState, options) {
  const rawHtml = await lexicalToEmailHtml(editorState);
  const sanitizedHtml = import_isomorphic_dompurify2.default.sanitize(rawHtml, EMAIL_SAFE_CONFIG);
  if (options?.wrapInTemplate) {
    return wrapInEmailTemplate(sanitizedHtml, options.preheader);
  }
  return sanitizedHtml;
}
async function lexicalToEmailHtml(editorState) {
  const { root } = editorState;
  if (!root || !root.children) {
    return "";
  }
  const html = root.children.map((node) => convertNode(node)).join("");
  return html;
}
function convertNode(node) {
  switch (node.type) {
    case "paragraph":
      return convertParagraph(node);
    case "heading":
      return convertHeading(node);
    case "list":
      return convertList(node);
    case "listitem":
      return convertListItem(node);
    case "blockquote":
      return convertBlockquote(node);
    case "text":
      return convertText(node);
    case "link":
      return convertLink(node);
    case "linebreak":
      return "<br>";
    default:
      if (node.children) {
        return node.children.map(convertNode).join("");
      }
      return "";
  }
}
function convertParagraph(node) {
  const align = getAlignment(node.format);
  const children = node.children?.map(convertNode).join("") || "";
  if (!children.trim()) {
    return '<p style="margin: 0 0 16px 0; min-height: 1em;">&nbsp;</p>';
  }
  return `<p style="margin: 0 0 16px 0; text-align: ${align};">${children}</p>`;
}
function convertHeading(node) {
  const tag = node.tag || "h1";
  const align = getAlignment(node.format);
  const children = node.children?.map(convertNode).join("") || "";
  const styles2 = {
    h1: "font-size: 32px; font-weight: 700; margin: 0 0 24px 0; line-height: 1.2;",
    h2: "font-size: 24px; font-weight: 600; margin: 0 0 16px 0; line-height: 1.3;",
    h3: "font-size: 20px; font-weight: 600; margin: 0 0 12px 0; line-height: 1.4;"
  };
  const style = `${styles2[tag] || styles2.h3} text-align: ${align};`;
  return `<${tag} style="${style}">${children}</${tag}>`;
}
function convertList(node) {
  const tag = node.listType === "number" ? "ol" : "ul";
  const children = node.children?.map(convertNode).join("") || "";
  const style = tag === "ul" ? "margin: 0 0 16px 0; padding-left: 24px; list-style-type: disc;" : "margin: 0 0 16px 0; padding-left: 24px; list-style-type: decimal;";
  return `<${tag} style="${style}">${children}</${tag}>`;
}
function convertListItem(node) {
  const children = node.children?.map(convertNode).join("") || "";
  return `<li style="margin: 0 0 8px 0;">${children}</li>`;
}
function convertBlockquote(node) {
  const children = node.children?.map(convertNode).join("") || "";
  const style = "margin: 0 0 16px 0; padding-left: 16px; border-left: 4px solid #e5e7eb; color: #6b7280;";
  return `<blockquote style="${style}">${children}</blockquote>`;
}
function convertText(node) {
  let text = escapeHtml(node.text || "");
  if (node.format & 1) {
    text = `<strong>${text}</strong>`;
  }
  if (node.format & 2) {
    text = `<em>${text}</em>`;
  }
  if (node.format & 8) {
    text = `<u>${text}</u>`;
  }
  if (node.format & 4) {
    text = `<strike>${text}</strike>`;
  }
  return text;
}
function convertLink(node) {
  const children = node.children?.map(convertNode).join("") || "";
  const url = node.fields?.url || "#";
  return `<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${children}</a>`;
}
function getAlignment(format) {
  if (!format) return "left";
  if (format & 2) return "center";
  if (format & 3) return "right";
  if (format & 4) return "justify";
  return "left";
}
function escapeHtml(text) {
  const map = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#039;"
  };
  return text.replace(/[&<>"']/g, (m) => map[m]);
}
function wrapInEmailTemplate(content, preheader) {
  return `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Email</title>
  <!--[if mso]>
  <noscript>
    <xml>
      <o:OfficeDocumentSettings>
        <o:PixelsPerInch>96</o:PixelsPerInch>
      </o:OfficeDocumentSettings>
    </xml>
  </noscript>
  <![endif]-->
</head>
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 16px; line-height: 1.5; color: #333333; background-color: #f3f4f6;">
  ${preheader ? `<div style="display: none; max-height: 0; overflow: hidden;">${escapeHtml(preheader)}</div>` : ""}
  <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin: 0; padding: 0;">
    <tr>
      <td align="center" style="padding: 20px 0;">
        <table role="presentation" cellpadding="0" cellspacing="0" width="600" style="margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden;">
          <tr>
            <td style="padding: 40px 30px;">
              ${content}
            </td>
          </tr>
        </table>
      </td>
    </tr>
  </table>
</body>
</html>`;
}

// src/endpoints/broadcasts/test.ts
var createTestBroadcastEndpoint = (config, collectionSlug) => {
  return {
    path: `/${collectionSlug}/:id/test`,
    method: "post",
    handler: async (req) => {
      try {
        const auth = await requireAdmin(req, config);
        if (!auth.authorized) {
          return Response.json({
            success: false,
            error: auth.error
          }, { status: 401 });
        }
        const url = new URL(req.url || "", `http://localhost`);
        const pathParts = url.pathname.split("/");
        const id = pathParts[pathParts.length - 2];
        if (!id) {
          return Response.json({
            success: false,
            error: "Broadcast ID is required"
          }, { status: 400 });
        }
        const data = await (req.json?.() || Promise.resolve({}));
        const testEmail = data.email || auth.user.email;
        if (!testEmail) {
          return Response.json({
            success: false,
            error: "No email address available for test send"
          }, { status: 400 });
        }
        const broadcast = await req.payload.findByID({
          collection: collectionSlug,
          id,
          user: auth.user
        });
        if (!broadcast) {
          return Response.json({
            success: false,
            error: "Broadcast not found"
          }, { status: 404 });
        }
        const channel = await req.payload.findByID({
          collection: "channels",
          id: typeof broadcast.channel === "string" ? broadcast.channel : broadcast.channel.id,
          user: auth.user
        });
        const htmlContent = await convertToEmailSafeHtml(broadcast.content, {
          wrapInTemplate: true,
          preheader: broadcast.preheader
        });
        const emailService = req.payload.newsletterEmailService;
        if (!emailService) {
          return Response.json({
            success: false,
            error: "Email service is not configured"
          }, { status: 500 });
        }
        await emailService.send({
          to: testEmail,
          from: channel?.fromEmail || config.providers.resend?.fromAddress || "noreply@example.com",
          fromName: channel?.fromName || config.providers.resend?.fromName || "Newsletter",
          replyTo: broadcast.settings?.replyTo || channel?.replyTo,
          subject: `[TEST] ${broadcast.subject}`,
          html: htmlContent,
          trackOpens: false,
          trackClicks: false
        });
        return Response.json({
          success: true,
          message: `Test email sent to ${testEmail}`
        });
      } catch (error) {
        console.error("Failed to send test broadcast:", error);
        return Response.json({
          success: false,
          error: "Failed to send test email"
        }, { status: 500 });
      }
    }
  };
};

// src/endpoints/broadcasts/index.ts
var createBroadcastManagementEndpoints = (config) => {
  if (!config.features?.newsletterManagement?.enabled) {
    return [];
  }
  const collectionSlug = config.features.newsletterManagement.collections?.broadcasts || "broadcasts";
  return [
    createSendBroadcastEndpoint(config, collectionSlug),
    createScheduleBroadcastEndpoint(config, collectionSlug),
    createTestBroadcastEndpoint(config, collectionSlug)
  ];
};

// src/endpoints/index.ts
function createNewsletterEndpoints(config) {
  const endpoints = [
    createSubscribeEndpoint(config),
    createUnsubscribeEndpoint(config)
  ];
  if (config.auth?.enabled !== false) {
    endpoints.push(
      createVerifyMagicLinkEndpoint(config),
      createPreferencesEndpoint(config),
      createUpdatePreferencesEndpoint(config),
      createSigninEndpoint(config),
      createMeEndpoint(config),
      createSignoutEndpoint(config)
    );
  }
  endpoints.push(...createBroadcastManagementEndpoints(config));
  return endpoints;
}

// src/fields/newsletterScheduling.ts
function createNewsletterSchedulingFields(config) {
  const groupName = config.features?.newsletterScheduling?.fields?.groupName || "newsletterScheduling";
  const contentField = config.features?.newsletterScheduling?.fields?.contentField || "content";
  const createMarkdownField = config.features?.newsletterScheduling?.fields?.createMarkdownField !== false;
  const fields = [
    {
      name: groupName,
      type: "group",
      label: "Newsletter Scheduling",
      admin: {
        condition: (data, { user }) => user?.collection === "users"
        // Only show for admin users
      },
      fields: [
        {
          name: "scheduled",
          type: "checkbox",
          label: "Schedule for Newsletter",
          defaultValue: false,
          admin: {
            description: "Schedule this content to be sent as a newsletter"
          }
        },
        {
          name: "scheduledDate",
          type: "date",
          label: "Send Date",
          required: true,
          admin: {
            date: {
              pickerAppearance: "dayAndTime"
            },
            condition: (data) => data?.[groupName]?.scheduled,
            description: "When to send this newsletter"
          }
        },
        {
          name: "sentDate",
          type: "date",
          label: "Sent Date",
          admin: {
            readOnly: true,
            condition: (data) => data?.[groupName]?.sendStatus === "sent",
            description: "When this newsletter was sent"
          }
        },
        {
          name: "sendStatus",
          type: "select",
          label: "Status",
          options: [
            { label: "Draft", value: "draft" },
            { label: "Scheduled", value: "scheduled" },
            { label: "Sending", value: "sending" },
            { label: "Sent", value: "sent" },
            { label: "Failed", value: "failed" }
          ],
          defaultValue: "draft",
          admin: {
            readOnly: true,
            description: "Current send status"
          }
        },
        {
          name: "emailSubject",
          type: "text",
          label: "Email Subject",
          required: true,
          admin: {
            condition: (data) => data?.[groupName]?.scheduled,
            description: "Subject line for the newsletter email"
          }
        },
        {
          name: "preheader",
          type: "text",
          label: "Email Preheader",
          admin: {
            condition: (data) => data?.[groupName]?.scheduled,
            description: "Preview text that appears after the subject line"
          }
        },
        {
          name: "segments",
          type: "select",
          label: "Target Segments",
          hasMany: true,
          options: [
            { label: "All Subscribers", value: "all" },
            ...config.i18n?.locales?.map((locale) => ({
              label: `${locale.toUpperCase()} Subscribers`,
              value: locale
            })) || []
          ],
          defaultValue: ["all"],
          admin: {
            condition: (data) => data?.[groupName]?.scheduled,
            description: "Which subscriber segments to send to"
          }
        },
        {
          name: "testEmails",
          type: "array",
          label: "Test Email Recipients",
          admin: {
            condition: (data) => data?.[groupName]?.scheduled && data?.[groupName]?.sendStatus === "draft",
            description: "Send test emails before scheduling"
          },
          fields: [
            {
              name: "email",
              type: "email",
              required: true
            }
          ]
        }
      ]
    }
  ];
  if (createMarkdownField) {
    fields.push(createMarkdownFieldInternal({
      name: `${contentField}Markdown`,
      richTextField: contentField,
      label: "Email Content (Markdown)",
      admin: {
        position: "sidebar",
        condition: (data) => Boolean(data?.[contentField] && data?.[groupName]?.scheduled),
        description: "Markdown version for email rendering",
        readOnly: true
      }
    }));
  }
  return fields;
}
function createMarkdownFieldInternal(config) {
  return {
    name: config.name,
    type: "textarea",
    label: config.label || "Markdown",
    admin: {
      ...config.admin,
      description: config.admin?.description || "Auto-generated from rich text content"
    },
    hooks: {
      afterRead: [
        async ({ data }) => {
          if (data?.[config.richTextField]) {
            try {
              const { convertLexicalToMarkdown } = await import("@payloadcms/richtext-lexical");
              return convertLexicalToMarkdown({
                data: data[config.richTextField]
              });
            } catch {
              return "";
            }
          }
          return "";
        }
      ],
      beforeChange: [
        () => {
          return null;
        }
      ]
    }
  };
}

// src/jobs/sync-unsubscribes.ts
var createUnsubscribeSyncJob = (pluginConfig) => {
  return {
    slug: "sync-unsubscribes",
    label: "Sync Unsubscribes from Email Service",
    handler: async ({ req }) => {
      const subscribersSlug = pluginConfig.subscribersSlug || "subscribers";
      const emailService = req.payload.newsletterEmailService;
      if (!emailService) {
        console.error("Email service not configured");
        return {
          output: {
            syncedCount: 0
          }
        };
      }
      let syncedCount = 0;
      try {
        if (emailService.getProvider() === "broadcast") {
          console.warn("Starting Broadcast unsubscribe sync...");
          const broadcastConfig = pluginConfig.providers?.broadcast;
          if (!broadcastConfig) {
            throw new Error("Broadcast configuration not found");
          }
          const apiUrl = broadcastConfig.apiUrl.replace(/\/$/, "");
          const token = process.env.NODE_ENV === "production" ? broadcastConfig.tokens.production : broadcastConfig.tokens.development;
          let page = 1;
          let hasMore = true;
          while (hasMore) {
            const response = await fetch(
              `${apiUrl}/api/v1/subscribers.json?page=${page}`,
              {
                headers: {
                  "Authorization": `Bearer ${token}`
                }
              }
            );
            if (!response.ok) {
              throw new Error(`Broadcast API error: ${response.status}`);
            }
            const data = await response.json();
            const broadcastSubscribers = data.subscribers || [];
            for (const broadcastSub of broadcastSubscribers) {
              const payloadSubscribers = await req.payload.find({
                collection: subscribersSlug,
                where: {
                  email: {
                    equals: broadcastSub.email
                  }
                },
                limit: 1
              });
              if (payloadSubscribers.docs.length > 0) {
                const payloadSub = payloadSubscribers.docs[0];
                const broadcastUnsubscribed = !broadcastSub.is_active || broadcastSub.unsubscribed_at;
                const payloadUnsubscribed = payloadSub.subscriptionStatus === "unsubscribed";
                if (broadcastUnsubscribed && !payloadUnsubscribed) {
                  await req.payload.update({
                    collection: subscribersSlug,
                    id: payloadSub.id,
                    data: {
                      subscriptionStatus: "unsubscribed",
                      unsubscribedAt: broadcastSub.unsubscribed_at || (/* @__PURE__ */ new Date()).toISOString()
                    }
                  });
                  syncedCount++;
                  console.warn(`Unsubscribed: ${broadcastSub.email}`);
                }
              }
            }
            if (data.pagination && data.pagination.current < data.pagination.total_pages) {
              page++;
            } else {
              hasMore = false;
            }
          }
          console.warn(`Broadcast sync complete. Unsubscribed ${syncedCount} contacts.`);
        }
        if (emailService.getProvider() === "resend") {
          console.warn("Starting Resend unsubscribe sync...");
          const resendConfig = pluginConfig.providers?.resend;
          if (!resendConfig) {
            throw new Error("Resend configuration not found");
          }
          console.warn("Resend polling implementation needed - webhooks recommended");
        }
        if (pluginConfig.hooks?.afterUnsubscribeSync) {
          await pluginConfig.hooks.afterUnsubscribeSync({
            req,
            syncedCount
          });
        }
      } catch (error) {
        console.error("Unsubscribe sync error:", error);
        throw error;
      }
      return {
        output: {
          syncedCount
        }
      };
    }
  };
};

// src/collections/Channels.ts
var createChannelsCollection = (pluginConfig) => {
  const hasProviders = !!(pluginConfig.providers?.broadcast || pluginConfig.providers?.resend);
  return {
    slug: "channels",
    labels: {
      singular: "Channel",
      plural: "Channels"
    },
    admin: {
      useAsTitle: "name",
      description: "Newsletter channels/publications that can send broadcasts",
      defaultColumns: ["name", "fromEmail", "subscriberCount", "active"]
    },
    fields: [
      {
        name: "name",
        type: "text",
        required: true,
        admin: {
          description: "The name of this newsletter channel"
        }
      },
      {
        name: "description",
        type: "textarea",
        admin: {
          description: "A brief description of what this channel is about"
        }
      },
      {
        name: "fromName",
        type: "text",
        required: true,
        admin: {
          description: "The sender name that appears in emails"
        }
      },
      {
        name: "fromEmail",
        type: "email",
        required: true,
        admin: {
          description: "The sender email address"
        }
      },
      {
        name: "replyTo",
        type: "email",
        admin: {
          description: "Reply-to email address (optional)"
        }
      },
      {
        name: "providerType",
        type: "select",
        required: true,
        options: [
          ...pluginConfig.providers?.broadcast ? [{ label: "Broadcast", value: "broadcast" }] : [],
          ...pluginConfig.providers?.resend ? [{ label: "Resend", value: "resend" }] : []
        ],
        admin: {
          description: "Which email provider manages this channel"
        }
      },
      {
        name: "providerId",
        type: "text",
        admin: {
          readOnly: true,
          description: "ID from the email provider",
          condition: (data) => hasProviders && data?.providerId
        }
      },
      {
        name: "subscriberCount",
        type: "number",
        admin: {
          readOnly: true,
          description: "Number of active subscribers"
        },
        defaultValue: 0
      },
      {
        name: "active",
        type: "checkbox",
        defaultValue: true,
        admin: {
          description: "Whether this channel is currently active"
        }
      },
      {
        name: "settings",
        type: "group",
        fields: [
          {
            name: "defaultTrackOpens",
            type: "checkbox",
            defaultValue: true,
            admin: {
              description: "Track email opens by default for broadcasts in this channel"
            }
          },
          {
            name: "defaultTrackClicks",
            type: "checkbox",
            defaultValue: true,
            admin: {
              description: "Track link clicks by default for broadcasts in this channel"
            }
          },
          {
            name: "requireDoubleOptIn",
            type: "checkbox",
            defaultValue: false,
            admin: {
              description: "Require double opt-in for new subscribers"
            }
          }
        ]
      }
    ],
    hooks: {
      // Sync with provider on create
      afterChange: [
        async ({ doc, operation, req }) => {
          if (!hasProviders || operation !== "create") return doc;
          try {
            const provider = await getProvider(doc.providerType, pluginConfig);
            if (!provider) return doc;
            const providerChannel = await provider.createChannel({
              name: doc.name,
              description: doc.description,
              fromName: doc.fromName,
              fromEmail: doc.fromEmail,
              replyTo: doc.replyTo
            });
            await req.payload.update({
              collection: "channels",
              id: doc.id,
              data: {
                providerId: providerChannel.id,
                subscriberCount: providerChannel.subscriberCount || 0
              },
              req
            });
            return {
              ...doc,
              providerId: providerChannel.id,
              subscriberCount: providerChannel.subscriberCount || 0
            };
          } catch (error) {
            req.payload.logger.error("Failed to create channel in provider:", error);
            return doc;
          }
        }
      ],
      // Sync updates with provider
      beforeChange: [
        async ({ data, originalDoc, operation, req }) => {
          if (!hasProviders || !originalDoc?.providerId || operation !== "update") return data;
          try {
            const provider = await getProvider(originalDoc.providerType, pluginConfig);
            if (!provider) return data;
            const updates = {};
            if (data.name !== originalDoc.name) updates.name = data.name;
            if (data.description !== originalDoc.description) updates.description = data.description;
            if (data.fromName !== originalDoc.fromName) updates.fromName = data.fromName;
            if (data.fromEmail !== originalDoc.fromEmail) updates.fromEmail = data.fromEmail;
            if (data.replyTo !== originalDoc.replyTo) updates.replyTo = data.replyTo;
            if (Object.keys(updates).length > 0) {
              await provider.updateChannel(originalDoc.providerId, updates);
            }
          } catch (error) {
            req.payload.logger.error("Failed to update channel in provider:", error);
          }
          return data;
        }
      ],
      // Handle deletion
      afterDelete: [
        async ({ doc, req }) => {
          if (!hasProviders || !doc?.providerId) return doc;
          try {
            const provider = await getProvider(doc.providerType, pluginConfig);
            if (!provider) return doc;
            await provider.deleteChannel(doc.providerId);
          } catch (error) {
            req.payload.logger.error("Failed to delete channel from provider:", error);
          }
          return doc;
        }
      ]
    }
  };
};
async function getProvider(providerType, config) {
  if (providerType === "broadcast") {
    const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
    const providerConfig = config.providers?.broadcast;
    return providerConfig ? new BroadcastApiProvider2(providerConfig) : null;
  }
  if (providerType === "resend") {
    const { ResendBroadcastProvider: ResendBroadcastProvider2 } = await Promise.resolve().then(() => (init_broadcast3(), broadcast_exports2));
    const providerConfig = config.providers?.resend;
    return providerConfig ? new ResendBroadcastProvider2(providerConfig) : null;
  }
  return null;
}

// src/collections/Broadcasts.ts
init_types();

// src/fields/emailContent.ts
var import_richtext_lexical = require("@payloadcms/richtext-lexical");
var emailSafeFeatures = [
  // Basic text formatting
  (0, import_richtext_lexical.BoldFeature)(),
  (0, import_richtext_lexical.ItalicFeature)(),
  (0, import_richtext_lexical.UnderlineFeature)(),
  (0, import_richtext_lexical.StrikethroughFeature)(),
  // Links with simple configuration
  (0, import_richtext_lexical.LinkFeature)({
    fields: [{
      name: "url",
      type: "text",
      required: true,
      admin: {
        description: "Enter the full URL (including https://)"
      }
    }]
  }),
  // Lists
  (0, import_richtext_lexical.OrderedListFeature)(),
  (0, import_richtext_lexical.UnorderedListFeature)(),
  // Headings - limited to h1, h2, h3 for email compatibility
  (0, import_richtext_lexical.HeadingFeature)({
    enabledHeadingSizes: ["h1", "h2", "h3"]
  }),
  // Basic paragraph and alignment
  (0, import_richtext_lexical.ParagraphFeature)(),
  (0, import_richtext_lexical.AlignFeature)(),
  // Blockquotes
  (0, import_richtext_lexical.BlockquoteFeature)()
];
var createEmailContentField = (overrides) => {
  return {
    name: "content",
    type: "richText",
    required: true,
    editor: (0, import_richtext_lexical.lexicalEditor)({
      features: emailSafeFeatures
    }),
    admin: {
      description: "Email content with limited formatting for compatibility",
      ...overrides?.admin
    },
    ...overrides
  };
};

// src/collections/Broadcasts.ts
var createBroadcastsCollection = (pluginConfig) => {
  const hasProviders = !!(pluginConfig.providers?.broadcast || pluginConfig.providers?.resend);
  return {
    slug: "broadcasts",
    labels: {
      singular: "Broadcast",
      plural: "Broadcasts"
    },
    admin: {
      useAsTitle: "name",
      description: "Individual email campaigns sent to subscribers",
      defaultColumns: ["name", "subject", "status", "channel", "sentAt", "actions"]
    },
    fields: [
      {
        name: "channel",
        type: "relationship",
        relationTo: "channels",
        required: true,
        admin: {
          description: "The channel this broadcast belongs to"
        }
      },
      {
        name: "name",
        type: "text",
        required: true,
        admin: {
          description: "Internal name for this broadcast"
        }
      },
      {
        name: "subject",
        type: "text",
        required: true,
        admin: {
          description: "Email subject line"
        }
      },
      {
        name: "preheader",
        type: "text",
        admin: {
          description: "Preview text shown in email clients"
        }
      },
      createEmailContentField({
        admin: {
          description: "Email content"
        }
      }),
      {
        name: "emailPreview",
        type: "ui",
        admin: {
          components: {
            Field: "/src/components/Broadcasts/EmailPreviewField"
          }
        }
      },
      {
        name: "status",
        type: "select",
        required: true,
        defaultValue: "draft" /* DRAFT */,
        options: [
          { label: "Draft", value: "draft" /* DRAFT */ },
          { label: "Scheduled", value: "scheduled" /* SCHEDULED */ },
          { label: "Sending", value: "sending" /* SENDING */ },
          { label: "Sent", value: "sent" /* SENT */ },
          { label: "Failed", value: "failed" /* FAILED */ },
          { label: "Paused", value: "paused" /* PAUSED */ },
          { label: "Canceled", value: "canceled" /* CANCELED */ }
        ],
        admin: {
          readOnly: true,
          components: {
            Cell: "/src/components/Broadcasts/StatusBadge"
          }
        }
      },
      {
        name: "settings",
        type: "group",
        fields: [
          {
            name: "trackOpens",
            type: "checkbox",
            defaultValue: true,
            admin: {
              description: "Track when recipients open this email"
            }
          },
          {
            name: "trackClicks",
            type: "checkbox",
            defaultValue: true,
            admin: {
              description: "Track when recipients click links"
            }
          },
          {
            name: "replyTo",
            type: "email",
            admin: {
              description: "Override the channel reply-to address for this broadcast"
            }
          }
        ]
      },
      {
        name: "audienceIds",
        type: "array",
        fields: [
          {
            name: "audienceId",
            type: "text",
            required: true
          }
        ],
        admin: {
          description: "Target specific audience segments",
          condition: () => {
            return hasProviders;
          }
        }
      },
      {
        name: "analytics",
        type: "group",
        admin: {
          readOnly: true,
          condition: (data) => data?.status === "sent" /* SENT */
        },
        fields: [
          {
            name: "recipientCount",
            type: "number",
            defaultValue: 0
          },
          {
            name: "sent",
            type: "number",
            defaultValue: 0
          },
          {
            name: "delivered",
            type: "number",
            defaultValue: 0
          },
          {
            name: "opened",
            type: "number",
            defaultValue: 0
          },
          {
            name: "clicked",
            type: "number",
            defaultValue: 0
          },
          {
            name: "bounced",
            type: "number",
            defaultValue: 0
          },
          {
            name: "complained",
            type: "number",
            defaultValue: 0
          },
          {
            name: "unsubscribed",
            type: "number",
            defaultValue: 0
          }
        ]
      },
      {
        name: "sentAt",
        type: "date",
        admin: {
          readOnly: true,
          date: {
            displayFormat: "MMM d, yyyy h:mm a"
          }
        }
      },
      {
        name: "scheduledAt",
        type: "date",
        admin: {
          condition: (data) => data?.status === "scheduled" /* SCHEDULED */,
          date: {
            displayFormat: "MMM d, yyyy h:mm a"
          }
        }
      },
      {
        name: "providerId",
        type: "text",
        admin: {
          readOnly: true,
          description: "ID from the email provider",
          condition: (data) => hasProviders && data?.providerId
        }
      },
      {
        name: "providerData",
        type: "json",
        admin: {
          readOnly: true,
          condition: () => false
          // Hidden by default
        }
      },
      // UI Field for custom actions in list view
      {
        name: "actions",
        type: "ui",
        admin: {
          components: {
            Cell: "/src/components/Broadcasts/ActionsCell",
            Field: "/src/components/Broadcasts/EmptyField"
          },
          disableListColumn: false
        }
      }
    ],
    hooks: {
      // Sync with provider on create
      afterChange: [
        async ({ doc, operation, req }) => {
          if (!hasProviders || operation !== "create") return doc;
          try {
            const channel = await req.payload.findByID({
              collection: "channels",
              id: typeof doc.channel === "string" ? doc.channel : doc.channel.id,
              req
            });
            const provider = await getProvider2(channel.providerType, pluginConfig);
            if (!provider) return doc;
            const htmlContent = await convertToEmailSafeHtml(doc.content);
            const providerBroadcast = await provider.create({
              channelId: channel.providerId || channel.id,
              name: doc.name,
              subject: doc.subject,
              preheader: doc.preheader,
              content: htmlContent,
              trackOpens: doc.settings?.trackOpens,
              trackClicks: doc.settings?.trackClicks,
              replyTo: doc.settings?.replyTo,
              audienceIds: doc.audienceIds?.map((a) => a.audienceId)
            });
            await req.payload.update({
              collection: "broadcasts",
              id: doc.id,
              data: {
                providerId: providerBroadcast.id,
                providerData: providerBroadcast.providerData
              },
              req
            });
            return {
              ...doc,
              providerId: providerBroadcast.id,
              providerData: providerBroadcast.providerData
            };
          } catch (error) {
            req.payload.logger.error("Failed to create broadcast in provider:", error);
            return doc;
          }
        }
      ],
      // Sync updates with provider
      beforeChange: [
        async ({ data, originalDoc, operation, req }) => {
          if (!hasProviders || !originalDoc?.providerId || operation !== "update") return data;
          try {
            const channelId = data.channel || originalDoc.channel;
            const channel = await req.payload.findByID({
              collection: "channels",
              id: typeof channelId === "string" ? channelId : channelId.id,
              req
            });
            const provider = await getProvider2(channel.providerType, pluginConfig);
            if (!provider) return data;
            const capabilities = provider.getCapabilities();
            if (!capabilities.editableStatuses.includes(originalDoc.status)) {
              return data;
            }
            const updates = {};
            if (data.name !== originalDoc.name) updates.name = data.name;
            if (data.subject !== originalDoc.subject) updates.subject = data.subject;
            if (data.preheader !== originalDoc.preheader) updates.preheader = data.preheader;
            if (data.content !== originalDoc.content) {
              updates.content = await convertToEmailSafeHtml(data.content);
            }
            if (data.settings?.trackOpens !== originalDoc.settings?.trackOpens) {
              updates.trackOpens = data.settings.trackOpens;
            }
            if (data.settings?.trackClicks !== originalDoc.settings?.trackClicks) {
              updates.trackClicks = data.settings.trackClicks;
            }
            if (data.settings?.replyTo !== originalDoc.settings?.replyTo) {
              updates.replyTo = data.settings.replyTo;
            }
            if (JSON.stringify(data.audienceIds) !== JSON.stringify(originalDoc.audienceIds)) {
              updates.audienceIds = data.audienceIds?.map((a) => a.audienceId);
            }
            if (Object.keys(updates).length > 0) {
              await provider.update(originalDoc.providerId, updates);
            }
          } catch (error) {
            req.payload.logger.error("Failed to update broadcast in provider:", error);
          }
          return data;
        }
      ],
      // Handle deletion
      afterDelete: [
        async ({ doc, req }) => {
          if (!hasProviders || !doc?.providerId) return doc;
          try {
            const channel = await req.payload.findByID({
              collection: "channels",
              id: typeof doc.channel === "string" ? doc.channel : doc.channel.id,
              req
            });
            const provider = await getProvider2(channel.providerType, pluginConfig);
            if (!provider) return doc;
            const capabilities = provider.getCapabilities();
            if (capabilities.editableStatuses.includes(doc.status)) {
              await provider.delete(doc.providerId);
            }
          } catch (error) {
            req.payload.logger.error("Failed to delete broadcast from provider:", error);
          }
          return doc;
        }
      ]
    }
  };
};
async function getProvider2(providerType, config) {
  if (providerType === "broadcast") {
    const { BroadcastApiProvider: BroadcastApiProvider2 } = await Promise.resolve().then(() => (init_broadcast2(), broadcast_exports));
    const providerConfig = config.providers?.broadcast;
    return providerConfig ? new BroadcastApiProvider2(providerConfig) : null;
  }
  if (providerType === "resend") {
    const { ResendBroadcastProvider: ResendBroadcastProvider2 } = await Promise.resolve().then(() => (init_broadcast3(), broadcast_exports2));
    const providerConfig = config.providers?.resend;
    return providerConfig ? new ResendBroadcastProvider2(providerConfig) : null;
  }
  return null;
}

// src/index.ts
init_broadcast2();
init_broadcast3();

// src/utilities/session.ts
var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
var getTokenFromRequest = (req) => {
  const cookies = req.cookies || req.headers?.cookie;
  if (!cookies) return null;
  if (typeof cookies === "string") {
    const parsed = cookies.split(";").reduce((acc, cookie) => {
      const [key, value] = cookie.trim().split("=");
      acc[key] = value;
      return acc;
    }, {});
    return parsed["newsletter-auth"] || null;
  }
  return cookies["newsletter-auth"] || null;
};
var verifyToken = (token, secret) => {
  try {
    const decoded = import_jsonwebtoken2.default.verify(token, secret);
    return decoded;
  } catch {
    return null;
  }
};
var getServerSideAuth = async (context, secret) => {
  const token = getTokenFromRequest(context.req);
  if (!token) {
    return { subscriber: null, isAuthenticated: false };
  }
  const payloadSecret = secret || process.env.PAYLOAD_SECRET;
  if (!payloadSecret) {
    console.error("No secret provided for token verification");
    return { subscriber: null, isAuthenticated: false };
  }
  const decoded = verifyToken(token, payloadSecret);
  if (!decoded) {
    return { subscriber: null, isAuthenticated: false };
  }
  return {
    subscriber: decoded,
    isAuthenticated: true
  };
};
var requireAuth = (gssp) => {
  return async (context) => {
    const { isAuthenticated: isAuthenticated2, subscriber } = await getServerSideAuth(context);
    if (!isAuthenticated2) {
      return {
        redirect: {
          destination: "/auth/signin",
          permanent: false
        }
      };
    }
    if (gssp) {
      const result = await gssp(context);
      return {
        ...result,
        props: {
          ...result.props,
          subscriber
        }
      };
    }
    return {
      props: {
        subscriber
      }
    };
  };
};
var isAuthenticated = (req, secret) => {
  const token = getTokenFromRequest(req);
  if (!token) return false;
  const decoded = verifyToken(token, secret);
  return !!decoded;
};

// src/index.ts
var newsletterPlugin = (pluginConfig) => (incomingConfig) => {
  const config = {
    enabled: true,
    subscribersSlug: "subscribers",
    settingsSlug: "newsletter-settings",
    auth: {
      enabled: true,
      tokenExpiration: "7d",
      magicLinkPath: "/newsletter/verify",
      ...pluginConfig.auth
    },
    ...pluginConfig
  };
  if (!config.enabled) {
    return incomingConfig;
  }
  const subscribersCollection = createSubscribersCollection(config);
  const settingsGlobal = createNewsletterSettingsGlobal(config);
  let collections = [...incomingConfig.collections || [], subscribersCollection];
  if (config.features?.newsletterManagement?.enabled) {
    const channelsCollection = createChannelsCollection(config);
    const broadcastsCollection = createBroadcastsCollection(config);
    collections.push(channelsCollection, broadcastsCollection);
  }
  if (config.features?.newsletterScheduling?.enabled) {
    const targetCollections = config.features.newsletterScheduling.collections || "articles";
    const collectionsToExtend = Array.isArray(targetCollections) ? targetCollections : [targetCollections];
    const schedulingFields = createNewsletterSchedulingFields(config);
    collections = collections.map((collection) => {
      if (collectionsToExtend.includes(collection.slug)) {
        return {
          ...collection,
          fields: [
            ...collection.fields,
            ...schedulingFields
          ]
        };
      }
      return collection;
    });
  }
  const endpoints = createNewsletterEndpoints(config);
  const syncJob = config.features?.unsubscribeSync?.enabled ? createUnsubscribeSyncJob(config) : null;
  const modifiedConfig = {
    ...incomingConfig,
    collections,
    globals: [
      ...incomingConfig.globals || [],
      settingsGlobal
    ],
    endpoints: [
      ...incomingConfig.endpoints || [],
      ...endpoints
    ],
    jobs: syncJob ? {
      ...incomingConfig.jobs || {},
      tasks: [
        ...incomingConfig.jobs?.tasks || [],
        syncJob
      ],
      // Add cron schedule if specified
      autoRun: config.features?.unsubscribeSync?.schedule ? Array.isArray(incomingConfig.jobs?.autoRun) ? [...incomingConfig.jobs.autoRun, {
        cron: config.features.unsubscribeSync.schedule,
        queue: "newsletter-sync",
        limit: 100
      }] : typeof incomingConfig.jobs?.autoRun === "function" ? async (payload) => {
        const autoRunFn = incomingConfig.jobs.autoRun;
        const existingConfigs = await autoRunFn(payload);
        return [...existingConfigs, {
          cron: config.features.unsubscribeSync.schedule,
          queue: "newsletter-sync",
          limit: 100
        }];
      } : [{
        cron: config.features.unsubscribeSync.schedule,
        queue: "newsletter-sync",
        limit: 100
      }] : incomingConfig.jobs?.autoRun
    } : incomingConfig.jobs,
    onInit: async (payload) => {
      try {
        const settings = await payload.findGlobal({
          slug: config.settingsSlug || "newsletter-settings"
        });
        let emailServiceConfig;
        if (settings) {
          emailServiceConfig = {
            provider: settings.provider || config.providers.default,
            fromAddress: settings.fromAddress || config.providers.resend?.fromAddress || config.providers.broadcast?.fromAddress || "noreply@example.com",
            fromName: settings.fromName || config.providers.resend?.fromName || config.providers.broadcast?.fromName || "Newsletter",
            replyTo: settings.replyTo,
            resend: settings.provider === "resend" ? {
              apiKey: settings.resendSettings?.apiKey || config.providers.resend?.apiKey || "",
              audienceIds: settings.resendSettings?.audienceIds?.reduce((acc, item) => {
                acc[item.locale] = {
                  production: item.production,
                  development: item.development
                };
                return acc;
              }, {}) || config.providers.resend?.audienceIds
            } : config.providers.resend,
            broadcast: settings.provider === "broadcast" ? {
              apiUrl: settings.broadcastSettings?.apiUrl || config.providers.broadcast?.apiUrl || "",
              tokens: {
                production: settings.broadcastSettings?.productionToken || config.providers.broadcast?.tokens.production,
                development: settings.broadcastSettings?.developmentToken || config.providers.broadcast?.tokens.development
              }
            } : config.providers.broadcast
          };
        } else {
          emailServiceConfig = {
            provider: config.providers.default,
            fromAddress: config.providers.resend?.fromAddress || config.providers.broadcast?.fromAddress || "noreply@example.com",
            fromName: config.providers.resend?.fromName || config.providers.broadcast?.fromName || "Newsletter",
            resend: config.providers.resend,
            broadcast: config.providers.broadcast
          };
        }
        payload.newsletterEmailService = createEmailService(emailServiceConfig);
        console.warn("Newsletter plugin initialized with", payload.newsletterEmailService.getProvider(), "provider");
        if (config.features?.newsletterManagement?.enabled) {
          try {
            let broadcastProvider;
            if (config.features.newsletterManagement.provider) {
              broadcastProvider = config.features.newsletterManagement.provider;
            } else {
              const providerType = emailServiceConfig.provider || config.providers.default;
              if (providerType === "broadcast" && emailServiceConfig.broadcast) {
                broadcastProvider = new BroadcastApiProvider(emailServiceConfig.broadcast);
              } else if (providerType === "resend" && emailServiceConfig.resend) {
                broadcastProvider = new ResendBroadcastProvider(emailServiceConfig.resend);
              } else {
                throw new Error(`Unsupported broadcast provider: ${providerType}`);
              }
            }
            const payloadWithProvider = payload;
            payloadWithProvider.broadcastProvider = broadcastProvider;
            payloadWithProvider.newsletterProvider = broadcastProvider;
            console.warn("Broadcast management initialized with", broadcastProvider.name, "provider");
          } catch (error) {
            console.error("Failed to initialize broadcast management provider:", error);
          }
        }
      } catch (error) {
        console.error("Failed to initialize newsletter email service:", error);
      }
      if (incomingConfig.onInit) {
        await incomingConfig.onInit(payload);
      }
    }
  };
  return modifiedConfig;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  getServerSideAuth,
  getTokenFromRequest,
  isAuthenticated,
  newsletterPlugin,
  requireAuth,
  verifyToken
});
//# sourceMappingURL=index.cjs.map