portaly-email

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Portaly Vibe Invitation Email Integration

Portaly Vibe 邀请邮件集成

Use this skill to help a human user wire up the registration link from Portaly Vibe invitation emails to the right landing page.
使用此功能帮助用户将Portaly Vibe邀请邮件中的注册链接配置到正确的着陆页。

Concept

核心概念

When a creator's follower clicks the CTA in a Portaly invitation email, the request always hits Portaly first at
https://portaly.ai/r/{referralCode}
— that endpoint is the central click tracker (rate limit, click-event log, attribution). Portaly then 302-redirects the user to a waitlist landing page.
Two modes decide where that redirect lands:
ModeWhere the user landsSetup
A. Hosted (default)
https://portaly.ai/waitlist/{creatorSlug}
— Portaly-hosted page
None
B. Self-hosted
https://{vibe-coder-app}/waitlist/{creatorSlug}
— your app
Set
appBaseUrl
+ implement the page
Mode is per-merchant, decided by whether
creatorSubscriptionConfig.appBaseUrl
is set. Toggling mode takes effect within ~60 seconds (Portaly's edge cache TTL) and applies to every email already in flight.
当创作者的粉丝点击Portaly邀请邮件中的CTA按钮时,请求会先到达Portaly的
https://portaly.ai/r/{referralCode}
端点——这是中央点击追踪器(负责速率限制、点击事件日志、归因分析)。随后Portaly会通过302重定向将用户引导至等待列表着陆页。
两种模式决定了跳转的目标页面:
模式用户跳转至设置方式
A. 托管模式(默认)
https://portaly.ai/waitlist/{creatorSlug}
—— Portaly托管的页面
无需设置
B. 自托管模式
https://{vibe-coder-app}/waitlist/{creatorSlug}
—— 你的应用页面
设置
appBaseUrl
并实现对应页面
模式按商家单独设置,由
creatorSubscriptionConfig.appBaseUrl
是否存在决定。切换模式后约60秒生效(Portaly边缘缓存的TTL),且对所有已发出的邮件立即生效。

Email Types Reference

邮件类型参考

Portaly Vibe sends five email types on the merchant's behalf. Only the bottom two contain a registration link and use the Mode A/B redirect logic above — the rest are pure transactional notifications.
Template typeTriggered byContains a link?Common reason to disable
welcome_free
POST /admin/users/sync
upserts a user with no active subscription
NoThe vibe coder's app already sends its own welcome email
welcome_paid
Payment callback (status
completed
), or sync that adds an active subscription
NoThe vibe coder customizes the upgrade email in their own product
subscription_canceled
POST /subscriptions/{id}/cancel
, or self-service portal cancel
NoThe vibe coder wants control over cancellation timing/copy
follower_invitation
POST /api/creator-email/campaigns/{id}/send
Yes (Mode A/B)Rarely disabled — this is the campaign feature itself
waitlist_onboarding
POST /api/waitlist/{slug}
Yes (Mode A/B)Rarely disabled — confirms the signup
Portaly Vibe代表商家发送五种类型的邮件。只有最后两种包含注册链接,并采用上述A/B模式的跳转逻辑——其余均为纯事务性通知。
模板类型触发条件是否包含链接?常见禁用原因
welcome_free
POST /admin/users/sync
接口更新无有效订阅的用户
Vibe开发者的应用已自行发送欢迎邮件
welcome_paid
支付回调(状态为
completed
),或同步操作添加了有效订阅
Vibe开发者在自有产品中自定义了升级邮件
subscription_canceled
POST /subscriptions/{id}/cancel
接口调用,或自助门户取消订阅
Vibe开发者希望控制取消订阅的时机/文案
follower_invitation
POST /api/creator-email/campaigns/{id}/send
接口调用
(采用A/B模式)极少禁用——这是核心的活动功能
waitlist_onboarding
POST /api/waitlist/{slug}
接口调用
(采用A/B模式)极少禁用——用于确认注册

Disabling a template

禁用模板

Per merchant, per type:
bash
curl -X PUT https://portaly.ai/api/creator-email/templates/welcome_free \
  -H "Authorization: Bearer ${PORTALY_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
Re-enable by sending
{ "enabled": true }
. Disabling takes effect immediately for new triggers — already-enqueued outbox rows still send.
Avoiding double emails. If the vibe coder has their own welcome / upgrade / cancellation flow, disable the matching template before wiring
syncToPortaly
(see
portaly-user
) or the payment callback handler (see
portaly-payment
). Otherwise every existing user the first bulk sync touches gets one Portaly
welcome_free
, and every successful checkout gets one Portaly
welcome_paid
on top of the vibe coder's own message.
可按商家、按类型禁用:
bash
curl -X PUT https://portaly.ai/api/creator-email/templates/welcome_free \\
  -H "Authorization: Bearer ${PORTALY_API_KEY}" \\
  -H "Content-Type: application/json" \\
  -d '{ "enabled": false }'
发送
{ "enabled": true }
即可重新启用。禁用后对新触发的邮件立即生效——已进入发件队列的邮件仍会发送。
避免重复邮件。如果Vibe开发者自有欢迎/升级/取消订阅流程,请在配置
syncToPortaly
(详见
portaly-user
)或支付回调处理器(详见
portaly-payment
之前禁用对应的模板。否则首次批量同步时,每个现有用户都会收到一封Portaly的
welcome_free
邮件,每笔成功结账都会在Vibe开发者自有邮件之外再收到一封Portaly的
welcome_paid
邮件。

API Host

API主机

https://portaly.ai
https://portaly.ai

Authentication

身份验证

Same Creator Subscription API Key (
pcs_live_*
/
pcs_test_*
) used by
portaly-payment
.
使用与
portaly-payment
相同的创作者订阅API密钥(
pcs_live_*
/
pcs_test_*
)。

Workflow

工作流程

Step 1 — Choose Mode

步骤1 —— 选择模式

Before writing any code, ask the human user which mode they want and wait for an explicit answer:
Portaly Vibe sends invitation emails on behalf of creators. The CTA in those emails goes through Portaly for click tracking, then redirects to a waitlist landing page. You have two options:
  • A. Hosted (recommended for fastest launch) — Use Portaly's hosted waitlist page. No server-side work. The page is generic but functional. Best when you don't have a brand reason to host it yourself.
  • B. Self-hosted (recommended for brand consistency) — Host
    /waitlist/[creatorSlug]
    on your own domain. Full control over UI, copy, and post-signup flow. Requires implementing the page and registering your
    appBaseUrl
    with Portaly.
Which would you like? You can switch later.
If the user picks A, jump to Mode A — Hosted CTA. If B, jump to Mode B — Self-hosted Waitlist.

在编写任何代码之前,询问用户想要哪种模式并等待明确答复:
Portaly Vibe代表创作者发送邀请邮件。邮件中的CTA按钮会先经过Portaly进行点击追踪,再重定向至等待列表着陆页。你有两个选项:
  • A. 托管模式(推荐快速上线) —— 使用Portaly托管的等待列表页面。无需服务器端开发。页面为通用样式但功能完整。适合无品牌化需求的场景。
  • B. 自托管模式(推荐保持品牌一致性) —— 在自有域名下托管
    /waitlist/[creatorSlug]
    页面。可完全控制UI、文案和注册后流程。需要实现该页面并向Portaly注册
    appBaseUrl
你想要选择哪种?后续可随时切换。
如果用户选择A,跳转至模式A —— 托管CTA;如果选择B,跳转至模式B —— 自托管等待列表

Mode A — Hosted CTA

模式A —— 托管CTA

See
references/hosted-cta.md
for full snippets.
What to do:
  1. Confirm
    appBaseUrl
    is empty
    (it is by default). If the merchant previously enabled Mode B, clear it:
    • Vibe MCP (preferred): call
      vibe_update_brand
      with
      { "appBaseUrl": "" }
      — no API key needed.
    • REST fallback:
      PUT /api/creator-subscription/config
      with
      { "appBaseUrl": "" }
      .
  2. Find the creator's slug
    GET /api/creator-subscription/config
    returns the merchant config. The slug also appears in the Portaly Vibe Dashboard.
  3. Embed the CTA URL in the vibe coder's app, email signature, social bio, etc.:
    https://portaly.ai/waitlist/{creatorSlug}
  4. No server-side implementation needed. Portaly serves the page, accepts the signup form, and stores the waitlist row.
That's it for Mode A. The creator can start sending invitation emails immediately — every click lands on Portaly's hosted page.

完整代码片段详见
references/hosted-cta.md
操作步骤:
  1. 确认
    appBaseUrl
    为空
    (默认状态)。如果商家之前启用过模式B,请清空该值:
    • 优先使用Vibe MCP:调用
      vibe_update_brand
      接口并传入
      { "appBaseUrl": "" }
      ——无需API密钥。
    • REST备选方案:调用
      PUT /api/creator-subscription/config
      接口并传入
      { "appBaseUrl": "" }
  2. 查找创作者的slug——调用
    GET /api/creator-subscription/config
    接口返回商家配置。slug也可在Portaly Vibe控制台中查看。
  3. 将CTA URL嵌入Vibe开发者的应用、邮件签名、社交账号简介等位置:
    https://portaly.ai/waitlist/{creatorSlug}
  4. 无需服务器端实现。Portaly会提供页面、处理注册表单并存储等待列表数据。
模式A的操作到此结束。创作者可立即开始发送邀请邮件——所有点击都会跳转到Portaly的托管页面。

Mode B — Self-hosted Waitlist

模式B —— 自托管等待列表

See
references/self-hosted-waitlist.md
for complete code templates (Next.js, React SPA, plain HTML).
完整代码模板(Next.js、React SPA、纯HTML)详见
references/self-hosted-waitlist.md

Step B1 — Register
appBaseUrl

步骤B1 —— 注册
appBaseUrl

Vibe MCP (preferred): call
vibe_update_brand
with
{ "appBaseUrl": "https://your-app.example.com" }
— no
PORTALY_API_KEY
needed.
REST fallback:
bash
curl -X PUT https://portaly.ai/api/creator-subscription/config \
  -H "Authorization: Bearer ${PORTALY_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{ "appBaseUrl": "https://your-app.example.com" }'
Constraints (enforced by Portaly):
  • Must be HTTPS
  • Max 255 characters
  • Trailing slashes are stripped automatically
  • Empty string clears the field (= switches back to Mode A)
优先使用Vibe MCP:调用
vibe_update_brand
接口并传入
{ "appBaseUrl": "https://your-app.example.com" }
——无需
PORTALY_API_KEY
REST备选方案
bash
curl -X PUT https://portaly.ai/api/creator-subscription/config \\
  -H "Authorization: Bearer ${PORTALY_API_KEY}" \\
  -H "Content-Type: application/json" \\
  -d '{ "appBaseUrl": "https://your-app.example.com" }'
约束条件(由Portaly强制执行):
  • 必须为HTTPS协议
  • 最大长度255字符
  • 自动去除末尾斜杠
  • 空字符串会清空该字段(即切换回模式A)

Step B2 — Implement
/waitlist/[creatorSlug]

步骤B2 —— 实现
/waitlist/[creatorSlug]
页面

The path must be
/waitlist/{creatorSlug}
— Portaly's redirect target is hard-coded. Anything else and the user hits a 404.
The page receives query params from Portaly's redirect — preserve them when posting back:
ParamPurpose
ref
Referral code, must be passed back to attribute the signup
utm_source
Always
invitation
utm_campaign
Campaign id (optional)
utm_content
Outbox id, identifies the specific recipient (optional)
The page must call two Portaly endpoints:
  • GET https://portaly.ai/api/waitlist/{creatorSlug}
    — returns
    { data: { creator: { slug, merchantName }, count } }
    . Use it to render the headline (
    Join {merchantName}'s waitlist
    ) and signup count.
  • POST https://portaly.ai/api/waitlist/{creatorSlug}
    — body
    { email, name?, source?, ref? }
    . Returns
    { data: { joined, alreadyOnList, creator } }
    .
Both endpoints are public (no API key needed). The POST is rate-limited per IP (5/hour per creator) and per creator (200/hour total) — show the user a "try again shortly" message on
429
.
路径必须
/waitlist/{creatorSlug}
——Portaly的重定向目标是硬编码的。任何其他路径都会导致用户访问404页面。
页面会从Portaly的重定向中接收查询参数——提交表单时需保留这些参数:
参数用途
ref
推荐码,必须传回以实现注册归因
utm_source
固定为
invitation
utm_campaign
活动ID(可选)
utm_content
发件箱ID,用于识别具体收件人(可选)
页面必须调用两个Portaly接口:
  • GET https://portaly.ai/api/waitlist/{creatorSlug}
    ——返回
    { data: { creator: { slug, merchantName }, count } }
    。用于渲染标题(
    加入{merchantName}的等待列表
    )和注册人数。
  • POST https://portaly.ai/api/waitlist/{creatorSlug}
    ——请求体为
    { email, name?, source?, ref? }
    。返回
    { data: { joined, alreadyOnList, creator } }
两个接口均为公开接口(无需API密钥)。POST接口按IP(每个创作者每小时5次)和按创作者(每小时总共200次)进行速率限制——收到
429
响应时需向用户显示"请稍后重试"的提示。

Step B3 — Wire to user sync (optional but recommended)

步骤B3 —— 对接用户同步(可选但推荐)

The signup is a new user from your perspective. After the POST succeeds, fire-and-forget a
syncToPortaly([{ email, name, status: 'active' }])
call so the creator can see the new follower in the Portaly Dashboard. See portaly-user/SKILL.md Step 5 for the helper.
ts
// after POST /api/waitlist succeeds
syncToPortaly([{ email, name }]).catch((err) =>
  console.error('[Portaly Sync]', err)
)
从你的角度来看,注册用户是新用户。POST接口调用成功后,可发起一个
syncToPortaly([{ email, name, status: 'active' }])
的异步调用,以便创作者在Portaly控制台中查看新粉丝。详见portaly-user/SKILL.md 步骤5中的辅助工具。
ts
// 在POST /api/waitlist调用成功后
syncToPortaly([{ email, name }]).catch((err) =>
  console.error('[Portaly Sync]', err)
)

Step B4 — Verify

步骤B4 —— 验证

  1. From the creator's dashboard, send a test invitation email to your own inbox.
  2. Click the CTA link in the email.
  3. The browser should redirect through
    portaly.ai/r/...
    and land on
    https://your-app.example.com/waitlist/{slug}?ref=...&utm_source=invitation&...
    .
  4. Submit the form; check the Portaly Dashboard's waitlist tab to confirm the row.
  5. If
    syncToPortaly
    is wired, the user should also appear in the Dashboard's user list.

  1. 从创作者控制台发送测试邀请邮件到你自己的邮箱。
  2. 点击邮件中的CTA链接。
  3. 浏览器应通过
    portaly.ai/r/...
    进行重定向,最终跳转到
    https://your-app.example.com/waitlist/{slug}?ref=...&utm_source=invitation&...
  4. 提交表单;检查Portaly控制台的等待列表标签页确认数据已添加。
  5. 如果已对接
    syncToPortaly
    ,该用户也应出现在控制台的用户列表中。

Sending a Campaign (Vibe MCP)

发送活动(Vibe MCP)

Independent of Mode A/B above. This workflow drives outgoing follower-email campaigns — drafting an invitation, queueing it to a recipient list, and reading back analytics. Available only when the agent is connected to the creator's Vibe MCP server (the install instructions for that are in the Vibe dashboard's onboarding flow). Authentication is the MCP Bearer token; no
PORTALY_API_KEY
involved.
See
references/sending-campaigns.md
for a copy-pastable end-to-end run.
与上述A/B模式无关。此工作流用于发起粉丝邮件营销活动——起草邀请邮件、将其加入收件人队列、查看分析数据。仅当Agent连接到创作者的Vibe MCP服务器时可用(安装说明在Vibe控制台的引导流程中)。身份验证使用MCP Bearer令牌;无需
PORTALY_API_KEY
端到端运行示例详见
references/sending-campaigns.md

Tools

工具

ToolPurpose
vibe_list_campaigns
Inventory: drafts to act on, in-flight sends, completed history. Filter by
status
.
vibe_create_campaign
Create a new campaign in
draft
status. Takes
name
(required, creator-facing only) plus optional
description
and
aiContext
(drafting hints).
vibe_send_campaign
Persist
subject
+
bodyHtml
onto a draft, enqueue to all imported recipients, return one of four outcomes (see below).
vibe_get_campaign_analytics
Funnel + event totals + 30-day timeseries for one campaign.
工具用途
vibe_list_campaigns
活动清单:待处理草稿、进行中的发送、已完成历史记录。可按
status
过滤。
vibe_create_campaign
创建一个
draft
状态的新活动。需传入
name
(必填,仅面向创作者),可选传入
description
aiContext
(起草提示)。
vibe_send_campaign
subject
+
bodyHtml
保存到草稿中,加入所有导入收件人的队列,返回四种结果之一(见下文)。
vibe_get_campaign_analytics
单个活动的转化漏斗 + 事件总数 + 30天时间序列数据。

Workflow

工作流程

  1. Confirm intent with the creator — what's the campaign for? Is there an existing draft, or starting fresh? Call
    vibe_list_campaigns
    to check.
  2. Create the draft with
    vibe_create_campaign
    . Pass any context the creator gives (campaign angle, tone, must-mention deadline) into
    aiContext
    . The campaign starts empty — no subject, no body, no recipients.
  3. Hand recipient import to the dashboard. Tell the creator:
    Recipient import is in the Vibe dashboard's Email → Outreach tab. Open your campaign there, upload a CSV / Google Sheet / paste addresses, then come back and tell me when you're done. Recipient management is intentionally not exposed via MCP — column-mapping a CSV in chat is fragile and the dashboard already has a preview UI.
  4. Draft
    subject
    +
    bodyHtml
    with the creator. Constraints:
    • Subject ≤ 255 chars.
    • Body is HTML, ≤ 100,000 chars.
    • Must include
      {inviteUrl}
      somewhere in the body — that's the tracked invitation link the recipient clicks. Without it, you've shipped a CTA-less email.
    • Always-available placeholders:
      {customerName}
      ,
      {merchantName}
      ,
      {inviteUrl}
      . Any extra column the creator imported is exposed as
      {slug}
      — confirm those slugs with the creator before referencing them.
  5. Confirm with the creator before sending. Sending is irreversible and burns from their monthly quota + purchased credits. Show the subject, the rendered body (or a preview link), the recipient count.
  6. Call
    vibe_send_campaign
    with
    campaignId
    ,
    subject
    ,
    bodyHtml
    . Switch on the
    outcome
    :
OutcomeMeaningWhat to do
enqueued
Send is in flightTell the creator: enqueued N emails, M quota remaining. Optionally schedule a follow-up to call
vibe_get_campaign_analytics
after a few minutes.
campaign_not_found
id is wrong / belongs to another merchantRecheck
vibe_list_campaigns
.
no_recipients
Imports are emptyStep 3 wasn't completed. Send the creator back to Email → Outreach to import their list.
quota_exceeded
Recipients > remaining quotaResponse includes
remainingQuota
and
needed
. Tell the creator how short they are and that they can top up email credits in Email → Credits in the dashboard. Do NOT retry without the creator topping up.
  1. Read analytics with
    vibe_get_campaign_analytics(campaignId)
    once delivery has had time to register (a few minutes). The funnel goes
    imported → enqueued → delivered → opened → clicked → bounced → complained → signedUp → converted
    .
    signedUp
    only populates if the recipient hits the waitlist landing page (Mode A or B above);
    converted
    only fills if they later subscribe via
    portaly-payment
    .
  1. 与创作者确认意图——活动目的是什么?是否已有草稿,还是从零开始?调用
    vibe_list_campaigns
    接口检查。
  2. 创建草稿使用
    vibe_create_campaign
    接口。将创作者提供的任何上下文(活动角度、语气、必须提及的截止日期)传入
    aiContext
    。活动初始为空——无主题、无内容、无收件人。
  3. 引导创作者在控制台导入收件人。告知创作者:
    收件人导入在Vibe控制台的邮件 → 触达标签页中。打开你的活动,上传CSV/谷歌表格/粘贴邮箱地址,完成后告知我。 收件人管理未通过MCP开放——在聊天中处理CSV列映射容易出错,而控制台已有预览UI。
  4. 与创作者一起起草
    subject
    +
    bodyHtml
    。约束条件:
    • 主题≤255字符。
    • 内容为HTML格式,≤100,000字符。
    • 必须在内容中包含
      {inviteUrl}
      ——这是收件人点击的带追踪的邀请链接。如果缺少该占位符,邮件将没有CTA按钮。
    • 可用的默认占位符:
      {customerName}
      {merchantName}
      {inviteUrl}
      。创作者导入的任何额外列都会以
      {slug}
      形式暴露——引用前需与创作者确认这些slug。
  5. 发送前与创作者确认。发送操作不可撤销,且会消耗每月配额和购买的 credits。向创作者展示主题、渲染后的内容(或预览链接)、收件人数量。
  6. **调用
    vibe_send_campaign
    **接口,传入
    campaignId
    subject
    bodyHtml
    。根据
    outcome
    处理:
结果含义操作
enqueued
邮件已加入队列告知创作者:已加入N封邮件到队列,剩余M配额。可选安排后续调用
vibe_get_campaign_analytics
接口查看数据(几分钟后)。
campaign_not_found
ID错误/属于其他商家重新检查
vibe_list_campaigns
接口的结果。
no_recipients
未导入收件人步骤3未完成。引导创作者回到邮件 → 触达标签页导入收件人列表。
quota_exceeded
收件人数量超过剩余配额响应中包含
remainingQuota
needed
。告知创作者配额缺口,并说明可在控制台的邮件 → 额度标签页购买邮件 credits。未经创作者补充额度请勿重试。
  1. 查看分析数据在邮件发送一段时间后(几分钟)调用
    vibe_get_campaign_analytics(campaignId)
    接口。转化漏斗为
    导入 → 加入队列 → 送达 → 打开 → 点击 → 退回 → 投诉 → 注册 → 转化
    signedUp
    仅在收件人访问等待列表着陆页(模式A或B)时填充;
    converted
    仅在后续通过
    portaly-payment
    订阅时填充。

Guardrails for sending

发送注意事项

  • Always include
    {inviteUrl}
    .
    The whole point of the campaign is the click — without the placeholder, recipients see a wall of text with no CTA.
  • Confirm the recipient count before sending. A misplaced 0 in a CSV column or a stale draft can lead to mass-emailing the wrong list. Read it back to the creator: "About to send to N people imported on <date>. Proceed?"
  • Do not call
    vibe_send_campaign
    repeatedly to "retry" a
    quota_exceeded
    .
    That's a soft fail — the creator must top up first. Retrying without action just churns calls.
  • Subject lines for follower outreach matter more than for transactional email. Push back if the creator gives a generic "Update from {merchantName}" — suggest something tied to the campaign's angle.

  • 必须包含
    {inviteUrl}
    。活动的核心是获得点击——如果缺少该占位符,收件人只会看到无CTA的纯文本。
  • 发送前确认收件人数量。CSV列中的误输入或陈旧草稿可能导致向错误列表发送大量邮件。向创作者复述:"即将发送给<日期>导入的N位用户。是否继续?"
  • 请勿反复调用
    vibe_send_campaign
    接口重试
    quota_exceeded
    结果
    。这是软失败——创作者必须先补充配额。无操作重试只会增加调用次数。
  • 粉丝触达活动的主题比事务性邮件更重要。如果创作者给出通用的"来自{merchantName}的更新",请建议使用与活动角度相关的主题。

Switching Modes

切换模式

FromToAction
Mode A → Mode BSet
appBaseUrl
via
PUT /api/creator-subscription/config
Mode B → Mode ASet
appBaseUrl
to
""
via
PUT /api/creator-subscription/config
Switch propagates within ~60 seconds (Portaly's per-process cache TTL). In-flight emails immediately pick up the new mode on the next click — Portaly resolves the redirect target at click time, not at send time.
操作
模式A → 模式B通过
PUT /api/creator-subscription/config
接口设置
appBaseUrl
模式B → 模式A通过
PUT /api/creator-subscription/config
接口将
appBaseUrl
设置为
""
切换后约60秒生效(Portaly进程缓存的TTL)。已发出的邮件在下次点击时会立即应用新模式——Portaly在点击时解析跳转目标,而非发送时。

Guardrails

注意事项

  • HTTPS only for
    appBaseUrl
    .
    http://
    is rejected by Portaly.
    localhost
    cannot be used in production — for local dev use ngrok / Cloudflare Tunnel.
  • Path is fixed:
    /waitlist/{creatorSlug}
    exactly. Do not alias to
    /signup
    ,
    /join
    , etc. — Portaly redirects to the literal
    /waitlist/{slug}
    path.
  • Click tracking always runs through Portaly. Do not try to point the email CTA directly at your own domain to "skip"
    /r/{code}
    — you'll lose click analytics and rate limiting.
  • Preserve UTM and
    ref
    query params
    on the POST body in Mode B. Dropping them breaks campaign attribution on Portaly's side.
  • Do not skip user sync. A signup that's only stored on Portaly's waitlist row but missing from the creator's user list creates support pain when the creator wonders why a known follower doesn't show up in their dashboard.
  • appBaseUrl
    仅支持HTTPS
    http://
    会被Portaly拒绝。生产环境中无法使用
    localhost
    ——本地开发请使用ngrok/Cloudflare Tunnel。
  • 路径固定:必须为
    /waitlist/{creatorSlug}
    。请勿别名到
    /signup
    /join
    等路径——Portaly会重定向到字面意义上的
    /waitlist/{slug}
    路径。
  • 点击追踪始终通过Portaly。请勿尝试将邮件CTA直接指向自有域名以"跳过"
    /r/{code}
    ——这会丢失点击分析数据和速率限制功能。
  • 模式B中请保留UTM和
    ref
    查询参数
    。丢失这些参数会破坏Portaly端的活动归因。
  • 请勿跳过用户同步。仅存储在Portaly等待列表中但未出现在创作者用户列表中的注册用户会引发支持问题,创作者会疑惑为何已知粉丝未显示在控制台中。

Output Preferences

输出偏好

  • Always confirm Mode A vs Mode B with the human user before doing setup work.
  • For Mode A, prefer one short paragraph + the CTA URL. No code templates needed.
  • For Mode B, lean on
    references/self-hosted-waitlist.md
    instead of inlining all the code.
  • Keep secrets (API keys) out of chat — write
    .env
    instructions instead.
  • 在进行设置工作前,始终与用户确认模式A或模式B。
  • 对于模式A,优先使用简短段落+CTA URL。无需提供代码模板。
  • 对于模式B,优先引用
    references/self-hosted-waitlist.md
    而非内联所有代码。
  • 聊天中请勿泄露密钥(API密钥)——请编写
    .env
    配置说明。

Reference Documents

参考文档

  • references/hosted-cta.md
    — Mode A snippets and CTA placement examples.
  • references/self-hosted-waitlist.md
    — Mode B implementation templates for Next.js, React SPA, and plain HTML.
  • references/sending-campaigns.md
    — End-to-end campaign send via Vibe MCP, with body templates and outcome handling.
  • references/hosted-cta.md
    —— 模式A代码片段和CTA放置示例。
  • references/self-hosted-waitlist.md
    —— 模式B实现模板(Next.js、React SPA、纯HTML)。
  • references/sending-campaigns.md
    —— 通过Vibe MCP端到端发送活动的示例,包含内容模板和结果处理。",