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 ( + + Beehiiv + + + + + + + ); +} 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";