diff --git a/plugins/beehiiv/credentials.ts b/plugins/beehiiv/credentials.ts
new file mode 100644
index 00000000..156621ae
--- /dev/null
+++ b/plugins/beehiiv/credentials.ts
@@ -0,0 +1,4 @@
+export type BeehiivCredentials = {
+ BEEHIIV_API_KEY?: string;
+ BEEHIIV_PUBLICATION_ID?: string;
+};
diff --git a/plugins/beehiiv/icon.tsx b/plugins/beehiiv/icon.tsx
new file mode 100644
index 00000000..beb66e09
--- /dev/null
+++ b/plugins/beehiiv/icon.tsx
@@ -0,0 +1,18 @@
+export function BeehiivIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/plugins/beehiiv/index.ts b/plugins/beehiiv/index.ts
new file mode 100644
index 00000000..17fd2ab3
--- /dev/null
+++ b/plugins/beehiiv/index.ts
@@ -0,0 +1,307 @@
+import type { IntegrationPlugin } from "../registry";
+import { registerIntegration } from "../registry";
+import { BeehiivIcon } from "./icon";
+
+const beehiivPlugin: IntegrationPlugin = {
+ type: "beehiiv",
+ label: "Beehiiv",
+ description: "Newsletter platform for creators and publishers",
+
+ icon: BeehiivIcon,
+
+ formFields: [
+ {
+ id: "apiKey",
+ label: "API Key",
+ type: "password",
+ placeholder: "...",
+ configKey: "apiKey",
+ envVar: "BEEHIIV_API_KEY",
+ helpText: "Get your API key from ",
+ helpLink: {
+ text: "Beehiiv Dashboard",
+ url: "https://app.beehiiv.com/settings/workspace/api",
+ },
+ },
+ {
+ id: "publicationId",
+ label: "Publication ID",
+ type: "text",
+ placeholder: "pub_00000000-0000-0000-0000-000000000000",
+ configKey: "publicationId",
+ envVar: "BEEHIIV_PUBLICATION_ID",
+ helpText: "Get your publication ID from ",
+ helpLink: {
+ text: "Beehiiv Dashboard",
+ url: "https://app.beehiiv.com/settings/workspace/api",
+ },
+ },
+ ],
+
+ testConfig: {
+ getTestFunction: async () => {
+ const { testBeehiiv } = await import("./test");
+ return testBeehiiv;
+ },
+ },
+
+ actions: [
+ {
+ slug: "create-subscription",
+ label: "Create Subscription",
+ description: "Create a new subscription for a publication",
+ category: "Beehiiv",
+ stepFunction: "createSubscriptionStep",
+ stepImportPath: "create-subscription",
+ outputFields: [
+ { field: "id", description: "Subscription ID" },
+ { field: "email", description: "Subscriber email" },
+ { field: "status", description: "Subscription status" },
+ ],
+ configFields: [
+ {
+ key: "email",
+ label: "Email",
+ type: "template-input",
+ placeholder: "subscriber@example.com or {{NodeName.email}}",
+ example: "subscriber@example.com",
+ required: true,
+ },
+ {
+ key: "reactivateExisting",
+ label: "Reactivate Existing",
+ type: "select",
+ options: [
+ { value: "false", label: "No" },
+ { value: "true", label: "Yes" },
+ ],
+ defaultValue: "false",
+ },
+ {
+ key: "sendWelcomeEmail",
+ label: "Send Welcome Email",
+ type: "select",
+ options: [
+ { value: "false", label: "No" },
+ { value: "true", label: "Yes" },
+ ],
+ defaultValue: "false",
+ },
+ {
+ key: "doubleOptOverride",
+ label: "Double Opt-In",
+ type: "select",
+ options: [
+ { value: "not_set", label: "Use Publication Default" },
+ { value: "on", label: "On" },
+ { value: "off", label: "Off" },
+ ],
+ defaultValue: "not_set",
+ },
+ {
+ key: "tier",
+ label: "Subscription Tier",
+ type: "select",
+ options: [
+ { value: "free", label: "Free" },
+ { value: "premium", label: "Premium" },
+ ],
+ },
+ {
+ type: "group",
+ label: "Attribution",
+ fields: [
+ {
+ key: "utmSource",
+ label: "UTM Source",
+ type: "template-input",
+ placeholder: "WayneEnterprise",
+ example: "WayneEnterprise",
+ },
+ {
+ key: "utmMedium",
+ label: "UTM Medium",
+ type: "template-input",
+ placeholder: "organic",
+ example: "organic",
+ },
+ {
+ key: "utmCampaign",
+ label: "UTM Campaign",
+ type: "template-input",
+ placeholder: "fall_2022_promotion",
+ example: "fall_2022_promotion",
+ },
+ {
+ key: "referringSite",
+ label: "Referring Site",
+ type: "template-input",
+ placeholder: "www.example.com/blog",
+ example: "www.wayneenterprise.com/blog",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ slug: "get-subscription",
+ label: "Get Subscription",
+ description: "Get subscription details by email",
+ category: "Beehiiv",
+ stepFunction: "getSubscriptionStep",
+ stepImportPath: "get-subscription",
+ outputFields: [
+ { field: "id", description: "Subscription ID" },
+ { field: "email", description: "Subscriber email" },
+ { field: "status", description: "Subscription status" },
+ { field: "created", description: "Creation timestamp" },
+ { field: "subscriptionTier", description: "Subscription tier" },
+ ],
+ configFields: [
+ {
+ key: "email",
+ label: "Email",
+ type: "template-input",
+ placeholder: "subscriber@example.com or {{NodeName.email}}",
+ example: "subscriber@example.com",
+ required: true,
+ },
+ {
+ key: "expand",
+ label: "Include Additional Data",
+ type: "select",
+ options: [
+ { value: "none", label: "None" },
+ { value: "stats", label: "Stats" },
+ { value: "custom_fields", label: "Custom Fields" },
+ { value: "referrals", label: "Referrals" },
+ { value: "tags", label: "Tags" },
+ { value: "subscription_premium_tiers", label: "Premium Tiers" },
+ ],
+ defaultValue: "none",
+ },
+ ],
+ },
+ {
+ slug: "update-subscription",
+ label: "Update Subscription",
+ description: "Update subscription details by email",
+ category: "Beehiiv",
+ stepFunction: "updateSubscriptionStep",
+ stepImportPath: "update-subscription",
+ outputFields: [
+ { field: "id", description: "Subscription ID" },
+ { field: "email", description: "Subscriber email" },
+ { field: "status", description: "Subscription status" },
+ ],
+ configFields: [
+ {
+ key: "email",
+ label: "Email",
+ type: "template-input",
+ placeholder: "subscriber@example.com or {{NodeName.email}}",
+ example: "subscriber@example.com",
+ required: true,
+ },
+ {
+ key: "tier",
+ label: "Subscription Tier",
+ type: "select",
+ options: [
+ { value: "none", label: "No Change" },
+ { value: "free", label: "Free" },
+ { value: "premium", label: "Premium" },
+ ],
+ defaultValue: "none",
+ },
+ {
+ key: "unsubscribe",
+ label: "Unsubscribe",
+ type: "select",
+ options: [
+ { value: "false", label: "No" },
+ { value: "true", label: "Yes" },
+ ],
+ defaultValue: "false",
+ },
+ ],
+ },
+ {
+ slug: "add-subscription-tag",
+ label: "Add Subscription Tag",
+ description: "Add tags to a subscription",
+ category: "Beehiiv",
+ stepFunction: "addSubscriptionTagStep",
+ stepImportPath: "add-subscription-tag",
+ outputFields: [
+ { field: "id", description: "Subscription ID" },
+ { field: "email", description: "Subscriber email" },
+ { field: "tags", description: "Subscription tags" },
+ ],
+ configFields: [
+ {
+ key: "subscriptionId",
+ label: "Subscription ID",
+ type: "template-input",
+ placeholder: "{{GetSubscription.id}}",
+ example: "sub_00000000-0000-0000-0000-000000000000",
+ required: true,
+ },
+ {
+ key: "tags",
+ label: "Tag(s)",
+ type: "template-input",
+ placeholder: "Premium, VIP",
+ example: "Premium, Basic",
+ required: true,
+ },
+ ],
+ },
+ {
+ slug: "add-to-automation",
+ label: "Add to Automation",
+ description: "Add an existing subscription to an automation flow",
+ category: "Beehiiv",
+ stepFunction: "addToAutomationStep",
+ stepImportPath: "add-to-automation",
+ outputFields: [
+ { field: "id", description: "Journey ID" },
+ { field: "automationId", description: "Automation ID" },
+ { field: "status", description: "Journey status" },
+ ],
+ configFields: [
+ {
+ key: "automationId",
+ label: "Automation ID",
+ type: "template-input",
+ placeholder: "aut_00000000-0000-0000-0000-000000000000",
+ example: "aut_00000000-0000-0000-0000-000000000000",
+ required: true,
+ },
+ {
+ key: "subscriptionId",
+ label: "Subscription ID",
+ type: "template-input",
+ placeholder: "{{GetSubscription.id}}",
+ example: "sub_00000000-0000-0000-0000-000000000000",
+ required: true,
+ },
+ {
+ key: "doubleOptOverride",
+ label: "Double Opt-In",
+ type: "select",
+ options: [
+ { value: "not_set", label: "Use Publication Default" },
+ { value: "on", label: "On" },
+ { value: "off", label: "Off" },
+ ],
+ defaultValue: "not_set",
+ },
+ ],
+ },
+ ],
+};
+
+registerIntegration(beehiivPlugin);
+
+export default beehiivPlugin;
diff --git a/plugins/beehiiv/steps/add-subscription-tag.ts b/plugins/beehiiv/steps/add-subscription-tag.ts
new file mode 100644
index 00000000..607ee855
--- /dev/null
+++ b/plugins/beehiiv/steps/add-subscription-tag.ts
@@ -0,0 +1,127 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { BeehiivCredentials } from "../credentials";
+
+const BEEHIIV_API_URL = "https://api.beehiiv.com/v2";
+
+type SubscriptionData = {
+ id: string;
+ email: string;
+ status: string;
+ tags?: string[];
+};
+
+type AddSubscriptionTagResult =
+ | { success: true; id: string; email: string; tags: string[] }
+ | { success: false; error: string };
+
+export type AddSubscriptionTagCoreInput = {
+ subscriptionId: string;
+ tags: string;
+};
+
+export type AddSubscriptionTagInput = StepInput &
+ AddSubscriptionTagCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: AddSubscriptionTagCoreInput,
+ credentials: BeehiivCredentials
+): Promise {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_API_KEY is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_PUBLICATION_ID is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const tagsArray = input.tags
+ .split(",")
+ .map((tag) => tag.trim())
+ .filter((tag) => tag.length > 0);
+
+ if (tagsArray.length === 0) {
+ return {
+ success: false,
+ error: "At least one tag is required",
+ };
+ }
+
+ const response = await fetch(
+ `${BEEHIIV_API_URL}/publications/${publicationId}/subscriptions/${encodeURIComponent(input.subscriptionId)}/tags`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({ tags: tagsArray }),
+ }
+ );
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return {
+ success: false,
+ error: "Subscription not found",
+ };
+ }
+ const errorText = await response.text();
+ let errorMessage: string;
+ try {
+ const errorJson = JSON.parse(errorText);
+ errorMessage = errorJson.message || errorJson.error || errorText;
+ } catch {
+ errorMessage = errorText || `HTTP ${response.status}`;
+ }
+ return {
+ success: false,
+ error: errorMessage,
+ };
+ }
+
+ const result = (await response.json()) as { data: SubscriptionData };
+ return {
+ success: true,
+ id: result.data.id,
+ email: result.data.email,
+ tags: result.data.tags || [],
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to add subscription tag: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function addSubscriptionTagStep(
+ input: AddSubscriptionTagInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "beehiiv";
diff --git a/plugins/beehiiv/steps/add-to-automation.ts b/plugins/beehiiv/steps/add-to-automation.ts
new file mode 100644
index 00000000..63cab9d6
--- /dev/null
+++ b/plugins/beehiiv/steps/add-to-automation.ts
@@ -0,0 +1,147 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { BeehiivCredentials } from "../credentials";
+
+const BEEHIIV_API_URL = "https://api.beehiiv.com/v2";
+
+type AutomationJourneyData = {
+ id: string;
+ automation_id: string;
+ subscription_id?: string;
+ email?: string;
+ status: string;
+ started_at?: number;
+ completed_at?: number;
+};
+
+type AddToAutomationResult =
+ | {
+ success: true;
+ id: string;
+ automationId: string;
+ subscriptionId?: string;
+ email?: string;
+ status: string;
+ startedAt?: number;
+ }
+ | { success: false; error: string };
+
+export type AddToAutomationCoreInput = {
+ automationId: string;
+ subscriptionId: string;
+ doubleOptOverride?: string;
+};
+
+export type AddToAutomationInput = StepInput &
+ AddToAutomationCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: AddToAutomationCoreInput,
+ credentials: BeehiivCredentials
+): Promise {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_API_KEY is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_PUBLICATION_ID is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!input.subscriptionId) {
+ return {
+ success: false,
+ error: "Subscription ID is required",
+ };
+ }
+
+ try {
+ const body: Record = {
+ subscription_id: input.subscriptionId,
+ };
+
+ if (input.doubleOptOverride && input.doubleOptOverride !== "not_set") {
+ body.double_opt_override = input.doubleOptOverride;
+ }
+
+ const response = await fetch(
+ `${BEEHIIV_API_URL}/publications/${publicationId}/automations/${encodeURIComponent(input.automationId)}/journeys`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify(body),
+ }
+ );
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return {
+ success: false,
+ error: "Automation or subscription not found",
+ };
+ }
+ const errorText = await response.text();
+ let errorMessage: string;
+ try {
+ const errorJson = JSON.parse(errorText);
+ errorMessage = errorJson.message || errorJson.error || errorText;
+ } catch {
+ errorMessage = errorText || `HTTP ${response.status}`;
+ }
+ return {
+ success: false,
+ error: errorMessage,
+ };
+ }
+
+ const result = (await response.json()) as { data: AutomationJourneyData };
+ return {
+ success: true,
+ id: result.data.id,
+ automationId: result.data.automation_id,
+ ...(result.data.subscription_id && {
+ subscriptionId: result.data.subscription_id,
+ }),
+ ...(result.data.email && { email: result.data.email }),
+ status: result.data.status,
+ ...(result.data.started_at && { startedAt: result.data.started_at }),
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to add to automation: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function addToAutomationStep(
+ input: AddToAutomationInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "beehiiv";
diff --git a/plugins/beehiiv/steps/create-subscription.ts b/plugins/beehiiv/steps/create-subscription.ts
new file mode 100644
index 00000000..fbda64ff
--- /dev/null
+++ b/plugins/beehiiv/steps/create-subscription.ts
@@ -0,0 +1,153 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { BeehiivCredentials } from "../credentials";
+
+const BEEHIIV_API_URL = "https://api.beehiiv.com/v2";
+
+type SubscriptionData = {
+ id: string;
+ email: string;
+ status: string;
+ created: number;
+ subscription_tier: string;
+};
+
+type CreateSubscriptionResult =
+ | { success: true; id: string; email: string; status: string }
+ | { success: false; error: string };
+
+export type CreateSubscriptionCoreInput = {
+ email: string;
+ reactivateExisting?: string;
+ sendWelcomeEmail?: string;
+ utmSource?: string;
+ utmMedium?: string;
+ utmCampaign?: string;
+ referringSite?: string;
+ doubleOptOverride?: string;
+ tier?: string;
+};
+
+export type CreateSubscriptionInput = StepInput &
+ CreateSubscriptionCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: CreateSubscriptionCoreInput,
+ credentials: BeehiivCredentials
+): Promise {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_API_KEY is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_PUBLICATION_ID is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const body: Record = {
+ email: input.email,
+ };
+
+ if (input.reactivateExisting === "true") {
+ body.reactivate_existing = true;
+ }
+
+ if (input.sendWelcomeEmail === "true") {
+ body.send_welcome_email = true;
+ }
+
+ if (input.utmSource) {
+ body.utm_source = input.utmSource;
+ }
+
+ if (input.utmMedium) {
+ body.utm_medium = input.utmMedium;
+ }
+
+ if (input.utmCampaign) {
+ body.utm_campaign = input.utmCampaign;
+ }
+
+ if (input.referringSite) {
+ body.referring_site = input.referringSite;
+ }
+
+ if (input.doubleOptOverride && input.doubleOptOverride !== "not_set") {
+ body.double_opt_override = input.doubleOptOverride;
+ }
+
+ if (input.tier) {
+ body.tier = input.tier;
+ }
+
+ const response = await fetch(
+ `${BEEHIIV_API_URL}/publications/${publicationId}/subscriptions`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify(body),
+ }
+ );
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ let errorMessage: string;
+ try {
+ const errorJson = JSON.parse(errorText);
+ errorMessage = errorJson.message || errorJson.error || errorText;
+ } catch {
+ errorMessage = errorText || `HTTP ${response.status}`;
+ }
+ return {
+ success: false,
+ error: errorMessage,
+ };
+ }
+
+ const result = (await response.json()) as { data: SubscriptionData };
+ return {
+ success: true,
+ id: result.data.id,
+ email: result.data.email,
+ status: result.data.status,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to create subscription: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function createSubscriptionStep(
+ input: CreateSubscriptionInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "beehiiv";
diff --git a/plugins/beehiiv/steps/get-subscription.ts b/plugins/beehiiv/steps/get-subscription.ts
new file mode 100644
index 00000000..64a413fb
--- /dev/null
+++ b/plugins/beehiiv/steps/get-subscription.ts
@@ -0,0 +1,167 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { BeehiivCredentials } from "../credentials";
+
+const BEEHIIV_API_URL = "https://api.beehiiv.com/v2";
+
+type SubscriptionData = {
+ id: string;
+ email: string;
+ status: string;
+ created: number;
+ subscription_tier: string;
+ utm_source?: string;
+ utm_medium?: string;
+ utm_campaign?: string;
+ referring_site?: string;
+ referral_code?: string;
+ custom_fields?: Array<{ name: string; value: string }>;
+ tags?: string[];
+ stats?: {
+ emails_received: number;
+ open_rate: number;
+ click_through_rate: number;
+ };
+};
+
+type GetSubscriptionResult =
+ | {
+ success: true;
+ id: string;
+ email: string;
+ status: string;
+ created: number;
+ subscriptionTier: string;
+ utmSource?: string;
+ utmMedium?: string;
+ utmCampaign?: string;
+ referringSite?: string;
+ referralCode?: string;
+ customFields?: Array<{ name: string; value: string }>;
+ tags?: string[];
+ stats?: {
+ emailsReceived: number;
+ openRate: number;
+ clickThroughRate: number;
+ };
+ }
+ | { success: false; error: string };
+
+export type GetSubscriptionCoreInput = {
+ email: string;
+ expand?: string;
+};
+
+export type GetSubscriptionInput = StepInput &
+ GetSubscriptionCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: GetSubscriptionCoreInput,
+ credentials: BeehiivCredentials
+): Promise {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_API_KEY is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_PUBLICATION_ID is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const encodedEmail = encodeURIComponent(input.email);
+ let url = `${BEEHIIV_API_URL}/publications/${publicationId}/subscriptions/by_email/${encodedEmail}`;
+
+ if (input.expand && input.expand !== "none") {
+ url += `?expand[]=${input.expand}`;
+ }
+
+ const response = await fetch(url, {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ },
+ });
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return {
+ success: false,
+ error: "Subscription not found",
+ };
+ }
+ const errorText = await response.text();
+ let errorMessage: string;
+ try {
+ const errorJson = JSON.parse(errorText);
+ errorMessage = errorJson.message || errorJson.error || errorText;
+ } catch {
+ errorMessage = errorText || `HTTP ${response.status}`;
+ }
+ return {
+ success: false,
+ error: errorMessage,
+ };
+ }
+
+ const result = (await response.json()) as { data: SubscriptionData };
+ const data = result.data;
+
+ return {
+ success: true,
+ id: data.id,
+ email: data.email,
+ status: data.status,
+ created: data.created,
+ subscriptionTier: data.subscription_tier,
+ ...(data.utm_source && { utmSource: data.utm_source }),
+ ...(data.utm_medium && { utmMedium: data.utm_medium }),
+ ...(data.utm_campaign && { utmCampaign: data.utm_campaign }),
+ ...(data.referring_site && { referringSite: data.referring_site }),
+ ...(data.referral_code && { referralCode: data.referral_code }),
+ ...(data.custom_fields && { customFields: data.custom_fields }),
+ ...(data.tags && { tags: data.tags }),
+ ...(data.stats && {
+ stats: {
+ emailsReceived: data.stats.emails_received,
+ openRate: data.stats.open_rate,
+ clickThroughRate: data.stats.click_through_rate,
+ },
+ }),
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to get subscription: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function getSubscriptionStep(
+ input: GetSubscriptionInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "beehiiv";
diff --git a/plugins/beehiiv/steps/update-subscription.ts b/plugins/beehiiv/steps/update-subscription.ts
new file mode 100644
index 00000000..cb2e29e3
--- /dev/null
+++ b/plugins/beehiiv/steps/update-subscription.ts
@@ -0,0 +1,128 @@
+import "server-only";
+
+import { fetchCredentials } from "@/lib/credential-fetcher";
+import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
+import { getErrorMessage } from "@/lib/utils";
+import type { BeehiivCredentials } from "../credentials";
+
+const BEEHIIV_API_URL = "https://api.beehiiv.com/v2";
+
+type SubscriptionData = {
+ id: string;
+ email: string;
+ status: string;
+ created: number;
+ subscription_tier: string;
+};
+
+type UpdateSubscriptionResult =
+ | { success: true; id: string; email: string; status: string }
+ | { success: false; error: string };
+
+export type UpdateSubscriptionCoreInput = {
+ email: string;
+ tier?: string;
+ unsubscribe?: string;
+};
+
+export type UpdateSubscriptionInput = StepInput &
+ UpdateSubscriptionCoreInput & {
+ integrationId?: string;
+ };
+
+async function stepHandler(
+ input: UpdateSubscriptionCoreInput,
+ credentials: BeehiivCredentials
+): Promise {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_API_KEY is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error:
+ "BEEHIIV_PUBLICATION_ID is not configured. Please add it in Project Integrations.",
+ };
+ }
+
+ try {
+ const body: Record = {};
+
+ if (input.tier && input.tier !== "none") {
+ body.tier = input.tier;
+ }
+
+ if (input.unsubscribe === "true") {
+ body.unsubscribe = true;
+ }
+
+ const encodedEmail = encodeURIComponent(input.email);
+ const response = await fetch(
+ `${BEEHIIV_API_URL}/publications/${publicationId}/subscriptions/by_email/${encodedEmail}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify(body),
+ }
+ );
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ return {
+ success: false,
+ error: "Subscription not found",
+ };
+ }
+ const errorText = await response.text();
+ let errorMessage: string;
+ try {
+ const errorJson = JSON.parse(errorText);
+ errorMessage = errorJson.message || errorJson.error || errorText;
+ } catch {
+ errorMessage = errorText || `HTTP ${response.status}`;
+ }
+ return {
+ success: false,
+ error: errorMessage,
+ };
+ }
+
+ const result = (await response.json()) as { data: SubscriptionData };
+ return {
+ success: true,
+ id: result.data.id,
+ email: result.data.email,
+ status: result.data.status,
+ };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Failed to update subscription: ${getErrorMessage(error)}`,
+ };
+ }
+}
+
+export async function updateSubscriptionStep(
+ input: UpdateSubscriptionInput
+): Promise {
+ "use step";
+
+ const credentials = input.integrationId
+ ? await fetchCredentials(input.integrationId)
+ : {};
+
+ return withStepLogging(input, () => stepHandler(input, credentials));
+}
+
+export const _integrationType = "beehiiv";
diff --git a/plugins/beehiiv/test.ts b/plugins/beehiiv/test.ts
new file mode 100644
index 00000000..ea0973a7
--- /dev/null
+++ b/plugins/beehiiv/test.ts
@@ -0,0 +1,51 @@
+export async function testBeehiiv(credentials: Record) {
+ try {
+ const apiKey = credentials.BEEHIIV_API_KEY;
+ const publicationId = credentials.BEEHIIV_PUBLICATION_ID;
+
+ if (!apiKey) {
+ return {
+ success: false,
+ error: "BEEHIIV_API_KEY is required",
+ };
+ }
+
+ if (!publicationId) {
+ return {
+ success: false,
+ error: "BEEHIIV_PUBLICATION_ID is required",
+ };
+ }
+
+ // Verify API key by fetching the publication details
+ const response = await fetch(
+ `https://api.beehiiv.com/v2/publications/${publicationId}`,
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ },
+ }
+ );
+
+ if (response.ok) {
+ return { success: true };
+ }
+
+ if (response.status === 401) {
+ return { success: false, error: "Invalid API key" };
+ }
+
+ if (response.status === 404) {
+ return { success: false, error: "Publication not found" };
+ }
+
+ const error = await response.text();
+ return { success: false, error: error || `HTTP ${response.status}` };
+ } catch (error) {
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : String(error),
+ };
+ }
+}
diff --git a/plugins/index.ts b/plugins/index.ts
index c2b41249..2530655d 100644
--- a/plugins/index.ts
+++ b/plugins/index.ts
@@ -15,6 +15,7 @@
*/
import "./ai-gateway";
+import "./beehiiv";
import "./blob";
import "./fal";
import "./firecrawl";