SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor

自托管 Calendly 替代方案对比:日历同步与邮件

对比 Cal.com、Easy!Appointments、Rallly 和 DayOtter:重点查看双向日历同步与出站邮件,并解释 VPS 端口 25 被阻止时为何预约确认邮件仍显示成功却无法送达。

简短结论

自托管的 Calendly 替代方案必须完成一件 VPS 上的内部工具通常不会做的事:面向公网提供服务。预约页面就是产品本身。它从第一天起就需要真实域名和 TLS(传输层安全),还必须能够向从未听说过您服务器的人发送邮件。

有4个项目符合实际选择范围。Cal.com 与 Calendly 最接近,是个人顾问的默认选择。Easy!Appointments 更轻量,使用 PHP 和 MySQL,在1 GB VPS 上也能稳定运行。Rallly 是群组投票工具,完全没有预约页面。DayOtter 是最新加入的项目,是一个采用 AGPLv3 的日程安排平台,前面配有一个先确认的助手。

有两个问题决定了您是否真的能运行其中一个。它能否与您一直使用的日历进行双向同步?它能否发送邮件?第二个问题是大多数自托管预约系统悄然失败的地方,因此先讨论它。

出站邮件是最容易出问题的部分

预订确认邮件可能会进入陌生人的收件箱。这类事务性邮件会投递到 Gmail 或 Microsoft 365,而这些接收方会根据发送 IP 地址和您的 DNS 记录评估邮件来源。

直接从 VPS 发送邮件几乎从来都无法正常工作。大多数服务商会阻止新账户的出站 TCP 25 端口,因此连接会一直挂起,最后超时。即使 25 端口开放,新 VPS 地址也没有发送历史,大型邮件接收方通常会将来自未知主机商地址段的连接视为可疑。预订已写入数据库,页面也显示已确认,但没有人收到邮件。服务器端看不出任何故障,因此这类问题通常要到数周后,某位没有到场的客户反馈时才会被发现。

使用中继服务。任何事务性邮件服务商都可以,应用只需要主机名、端口、用户名和密码。在修改应用配置前,先确认端口可访问:

nc -vz -w 5 "$SMTP_HOST" 587

出现 succeeded 行表示路径已连通。连接挂起或出现 Connection refused 表示端口在网络层被阻止,编辑多少次 .env 都无法解决问题。中继服务使用 587 或 465 端口,正是因为 25 端口经常被阻止。

每个项目使用中继服务的方式不同。Cal.com 读取 EMAIL_FROMEMAIL_SERVER_HOSTEMAIL_SERVER_PORTEMAIL_SERVER_USEREMAIL_SERVER_PASSWORD,也接受 RESEND_API_KEY。请特别注意这一项:随附的 .env.example 会将 EMAIL_SERVER_HOST 指向 localhost1025 端口,而它是本地开发邮箱。保留默认值会导致应用无错误地将邮件发送到不存在的目标。Rallly 使用 SMTP_HOSTSMTP_PORTSMTP_USERSMTP_PWD。DayOtter 使用 SMTP 设置或 Resend 密钥。Easy!Appointments 从应用发送通知,因此在接受真实预订前,应先在其设置页面中将它指向同一个中继服务。

然后发布中继服务提供的 DNS 记录。SPF(发件人策略框架)记录用于声明哪些服务器可以代表您的域发送邮件,DKIM(域密钥识别邮件)密钥会对每封邮件签名,使接收方能够验证邮件未被篡改。确认两者都通过后,再添加 DMARC(基于域的消息认证、报告和一致性)策略。向大型邮件服务商的真实地址发送测试预订邮件,打开邮件头,并确认身份验证行显示为 pass。无法发送邮件的预订页面比没有预订页面更糟,因为它会静默失败。

哪些日历后端真正支持双向同步

同步有两个方向,而且两者可能分别失败。读取方向负责获取可用时间:应用必须能看到您现有的忙碌时间段,否则可能分配您已经占用的时段。写入方向负责创建预约:已确认的事件必须出现在您实际查看的日历中,而不能只存在于预约工具内。

Google Calendar 和 Microsoft 365 都支持双向同步,但自托管安装有一个前提。您必须自行创建 OAuth(开放授权)客户端,因为托管产品的客户端 ID 不在源代码中。对于 Cal.com,该客户端配置位于 GOOGLE_API_CREDENTIALS.env 中,其中保存您从 Google Cloud 控制台下载的 JSON 文件。DayOtter 对 Google 和 Microsoft 的 OAuth 凭据也采用相同方式。

这里有两个问题需要在开始前了解。第一,您注册的重定向 URI 必须与公网 URL 完全匹配,包括协议和末尾路径,否则 Google 会在授权页面显示 redirect_uri_mismatch 并停止连接。第二,发布状态保持为 Testing 的 Google 项目会签发 7 天后过期的刷新令牌。同步会正常工作一周,然后停止;应用在下一次刷新时会记录 invalid_grant。将授权页面切换为 In production,或者接受每周一手动重新连接。

CalDAV(WebDAV 的日历扩展)是开放选项,但支持范围较小。Cal.com 提供的 CalDAV 应用仍标记为 beta,已针对 Baikal、Radicale、Nextcloud 和 Kerio Connect 等服务器进行验证。Apple iCloud 也可通过同一应用使用,但需要应用专用密码,而不是 Apple ID 密码。DayOtter 将 Apple(通过 CalDAV)与 Google 和 Microsoft 365 并列列出。

ICS feed 不是同步。订阅的 .ics URL 按设计为只读,因此可以在预约页面上标记忙碌时间,但永远无法接收预约。如果工具只为您的日历提供 ICS,您只配置了一半功能,仍需手动复制事件。

Easy!Appointments 只能同步 Google Calendar。Rallly 完全不会读取可用时间,而是收集一组候选日期上的投票。对于“我们六个人什么时候可以见面”,它是合适的工具;对于“预约与我进行 30 分钟的会议”,它则不合适。

预约页面是公开服务,因此 TLS 必须优先配置

大多数自托管服务都是私有的。Wiki、看板和仪表盘都可以放在 VPN 或 SSO 登录之后,不直接暴露到公网。预约链接不能这样处理。任何收到链接的人都必须能够打开页面,因此配置会有 3 个具体变化。

安装任何软件之前,您需要一个 A 记录指向 VPS 的域名。第一天就需要配置证书,因为浏览器会将纯 HTTP 表单标记为不安全,而客户会在其中填写姓名和电子邮件地址。您还必须在应用配置中正确设置应用的公网 URL,因为该值会写入外发邮件中的链接以及 OAuth 重定向 URI。请在 Cal.com 中设置 NEXT_PUBLIC_WEBAPP_URL,在 Rallly 中设置 DOMAIN,在 Easy!Appointments 中设置 BASE_URL,或在安装时设置 DAYOTTER_DOMAIN,并将其设置为您实际使用的 https:// 地址。

Rallly 和 DayOtter 会为您处理 TLS。Rallly 的内置服务栈包含 Traefik,并使用 ACME_EMAIL 中的地址申请 Let's Encrypt 证书。DayOtter 的安装程序会启动 Caddy,并自动启用 HTTPS。Cal.com 和 Easy!Appointments 不会自动处理这些配置,因此您需要在前面配置 nginx,并像配置 使用 Certbot 在 nginx 上申请 Let's Encrypt 证书 一样自行申请证书。将应用容器绑定到 127.0.0.1,确保只能通过您控制的代理访问应用。如果同一台服务器已经运行 用于内部看板的自托管 Trello 替代方案,请让它继续受现有身份验证保护,并只为预约域名配置公网 server block。

在 VPS 上部署 Cal.com

Docker 配置位于独立仓库中,镜像已预先构建并发布到 Docker Hub,因此只需拉取镜像,无需构建。

git clone --recursive https://github.com/calcom/cal.diy.git
cd cal.diy
cp .env.example .env
openssl rand -base64 32
openssl rand -base64 24
docker compose pull
docker compose up -d

第一个随机值填入 NEXTAUTH_SECRET,第二个填入 CALENDSO_ENCRYPTION_KEY。两者都是必需的。设置 DATABASE_URL,并将 NEXT_PUBLIC_WEBAPP_URL 指向您的公网地址。随附的堆栈包含 Web 应用、PostgreSQL 和 Prisma Studio;文档提供了 docker compose up -d calcom,用于让应用单独运行并连接您在其他位置托管的数据库。安装稳定后,您应使用这种方式。

拉取镜像,不要在 VPS 上构建。项目自身的说明要求从源代码构建时导出 NODE_OPTIONS="--max-old-space-size=16384";这表示仅 Node 就需要 16 GB 堆。使用 ARM 硬件时,在镜像标签后添加 -arm 后缀。项目没有说明运行预构建镜像所需的最低资源,因此请将应用加 PostgreSQL 需要 2 GB 视为我的实际经验值,而不是官方文档中的要求,并在第一周监控内存使用情况。

确认服务已启动:

docker compose ps
docker compose logs -f calcom
curl -sI https://cal.example.com | head -n 1

curl 应输出 HTTP/2 200。如果容器显示正在运行,但 nginx 返回 502 Bad Gateway,通常表示首次启动仍在执行数据库迁移。等待几分钟,并读取日志后再判断服务是否损坏。Cal.com 会在每次预约确认时触发 Webhook,因此一次预约可以驱动您已有的自动化流程,例如 在您的 VPS 上通过 HTTPS 可访问的 n8n 实例

核心组件采用 AGPLv3 许可证,部分功能位于 enterprise 目录中,并采用单独的商业许可证。使用团队功能构建付费业务流程前,请先阅读该许可证。

1 GB 服务器上的 Easy!Appointments

要求包括 Apache 或 Nginx、PHP 8.2 或更高版本,以及 MySQL。官方镜像位于 alextselegidis/easyappointments

先说明一个注意事项。仓库中的 docker-compose.yml 是开发环境。它要求您进入容器中的 shell 并运行 npm install && composer install && npm start。这不是部署方式。请改用已发布的镜像:

services:
  easyappointments:
    image: alextselegidis/easyappointments  # pin the current tag from Docker Hub
    environment:
      - BASE_URL=https://book.example.com
      - DB_HOST=mysql
      - DB_NAME=easyappointments
      - DB_USERNAME=easyapp
      - DB_PASSWORD=change-me
    ports:
      - '127.0.0.1:8080:80'
    depends_on:
      - mysql
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=change-me-too
      - MYSQL_DATABASE=easyappointments
      - MYSQL_USER=easyapp
      - MYSQL_PASSWORD=change-me
    volumes:
      - ./mysql:/var/lib/mysql

BASE_URL 必须是公开的 HTTPS 地址。如果填写错误,确认邮件中的预约链接会指向客户无法访问的主机。该镜像在端口 80 上提供普通 HTTP,且不包含自己的证书,因此端口会绑定到 127.0.0.1,再由前置 nginx 终止 TLS。如果您不熟悉 compose 语法,请先阅读VPS 上的 Docker Compose 基础,然后再回来继续。

这是本文中资源占用最低的方案,优势非常明显。两个容器分别运行 PHP 应用和 MySQL,在 1 GB VPS 上也能稳定运行。但它的适用范围有限:唯一支持的日历后端是 Google Calendar,界面是传统管理面板,而不是现代预约流程。如果您使用 Microsoft 365、Fastmail 或 Nextcloud 日历,那么从一开始就不应选择此方案。

群组投票工具 Rallly

Rallly 解决的是另一个问题。它不会发布您的可用时间,而是向群组提供一组候选时间并收集投票。这适合安排董事会会议,但不适合用作客户预约链接。

curl -fsSL https://get.rallly.co | bash

将任何脚本通过管道传入 shell 前,先阅读脚本内容。将 bash 替换为 less,查看脚本的作用,然后再运行。手动安装路径执行相同的操作,但每一步都清晰可见:

git clone https://github.com/lukevella/rallly-selfhosted.git
cd rallly-selfhosted
./rallly.sh setup
./rallly.sh start

文档要求至少 2 GB RAM、Docker 19.03 或更高版本并使用 Compose v2、80 和 443 端口空闲,以及一个指向服务器的域名。随附的堆栈包括用于 HTTPS 的 Traefik、Web 应用、PostgreSQL,以及用于兼容 S3 的对象存储的 Garage。设置 DOMAIN、长度至少为 32 个字符的 SECRET_PASSWORDSUPPORT_EMAILINITIAL_ADMIN_EMAIL。如果您已经运行反向代理,请设置 PROXY_MODE=externalWEB_PORT,这样 Traefik 就不会干预。如果您已经运行 使用 MinIO 的自托管兼容 S3 对象存储,请将 S3_* 变量指向该存储,并移除 Garage 容器。

这里 SMTP 不是可选项,因为登录使用魔术链接。没有可用的中继服务器,任何人都无法登录,包括您刚创建的管理员账户。这种电子邮件故障的结果还算理想:它会在用户进入系统前阻止登录,而不是让您在三周后丢失客户的预约。

DayOtter,最新加入者

DayOtter 是一个附带助手的 AGPLv3 排程平台。生产环境安装只需执行一条命令:

curl -fsSL https://raw.githubusercontent.com/Dayotter/dayotter/main/deploy/install.sh \
  | sudo DAYOTTER_DOMAIN=cal.example.com bash

与上文一样,执行前请先阅读该命令。安装程序会配置 Docker、生成密钥,并启动完整技术栈:Next.js Web 应用、负责处理提醒、日历同步和 webhook 的后台 worker、PostgreSQL、Redis,以及自动配置 HTTPS 的 Caddy。

它支持的日历类型是四个平台中最广的,包括 Google、Microsoft 365、基于 CalDAV 的 Apple 日历和 ICS 源,但 ICS 仍有上文所述的限制。其他集成都必须通过环境变量显式启用,包括用于邮件的 SMTP 或 Resend、用于助手的 ANTHROPIC_API_KEY、用于短信的 Twilio,以及用于支付的 Stripe。助手采用先确认机制:它先提出操作,您批准后才会执行;未经明确确认,任何内容都不会写入日历。将 API key 留空后,产品的这部分功能不会运行。

对于自行托管用户,其许可方式很清晰。核心组件采用 AGPLv3,ee/ 目录包含仅限商业云服务的许可;只有设置 DAYOTTER_CLOUD=1 后,该许可才会生效。这意味着,截至 August 2026,托管方案中的团队功能(收费标准为每个席位每月 $9)也可在您自己的服务器上使用。

这也是这里技术栈最重、项目历史最短的一套方案。将它与现有预约链接并行运行两周,通过两套系统接收真实预约,并查看 worker 日志,然后再将客户迁移过来。

这套技术栈的实际成本

容器数量是衡量一套技术栈会占用小型 VPS 多少资源的可靠指标,因为每个服务都有自己的最低内存需求。以下数量来自各项目自行发布的 Docker 技术栈,数据读取于 2026 年 8 月。

ChartServices in each project's documented Docker stack
The data behind this chart
[
  {
    "tool": "Easy!Appointments",
    "containers": 2,
    "database": "MySQL"
  },
  {
    "tool": "Cal.com",
    "containers": 3,
    "database": "PostgreSQL"
  },
  {
    "tool": "Rallly",
    "containers": 4,
    "database": "PostgreSQL"
  },
  {
    "tool": "DayOtter",
    "containers": 5,
    "database": "PostgreSQL and Redis"
  }
]

Easy!Appointments 需要 2 个容器,1 GB 内存即可运行。Rallly 的捆绑技术栈包含 4 个容器,其文档要求 2 GB 内存。DayOtter 的安装程序会启动 5 个容器,因此在这里的 4 个项目中,它需要配置最高的服务器。Cal.com 和 DayOtter 都没有公布最低内存要求,因此我对这两个项目都从 2 GB 开始配置,但这不代表官方支持的数值。

如果您已经运行基础设施,其中两个项目的容器数量可以减少。将 Rallly 指向您自己的代理和对象存储后,可以移除它的 Traefik 和 Garage 容器。Cal.com 的 Prisma Studio 是开发工具,不应在公网服务器上持续运行。

应选择哪种自托管 Calendly 替代方案

个人顾问应运行 Cal.com。 在这里列出的项目中,只有它同时提供了用户熟悉的预约页面、可避免在 VPS 上构建 Node 的预构建镜像,以及适合不使用 Google 或 Microsoft 日历用户的 CalDAV 支持路径。一个 PostgreSQL 数据库和一个应用容器,维护负担足以长期承担。请预留一个下午配置 OAuth 客户端和邮件中继,并注意 CalDAV 应用仍处于 beta 阶段。因此,在发布链接前,应端到端测试一次真实预约。

小型团队应考虑 DayOtter。 加权轮询和集体预约都包含在 AGPLv3 核心中,因此自托管即可获得托管服务按席位收费的功能;其 worker 进程也针对团队实际依赖的提醒和 Webhook 进行了设计。代价是成熟度较低:它是本文列表中最新的项目。因此应先并行运行,并在完整观察一个月的预约情况前保留旧链接。

还有两种更具体的情况。如果您只需要通过投票确定团队可会面的时间,安装 Rallly 即可。如果您使用 1 GB VPS、依赖 Google Calendar,并且希望使用能够处理预约的最小系统,Easy!Appointments 的使用寿命会超过您能部署在该服务器上的所有更复杂方案。关于同一台服务器还值得自托管哪些服务,请参阅 2026 年值得自托管的服务

FAQ

我可以在没有域名的情况下运行自托管预约页面吗?

不可以。这些应用都会将公网 URL 写入确认邮件中的链接,Google 和 Microsoft 也会根据同一个值匹配 OAuth 重定向 URI,因此直接使用 IP 地址会在授权页面显示 redirect_uri_mismatch。Let's Encrypt 也不会为 IP 地址签发证书,所以页面只能通过普通 HTTP 加载,浏览器会将表单标记为不安全。先购买域名,将 A 记录指向 VPS,然后再安装。

为什么我始终收不到预约确认邮件?

几乎总是因为服务器正在尝试自行投递邮件。大多数 VPS 提供商会阻止新账户的出站 25 端口,因此连接会一直等待;即使端口开放,新地址也没有发信信誉,大型邮件接收方仍会拒收。将应用配置为通过 587 端口使用事务型邮件中继,使用 nc -vz -w 5 "$SMTP_HOST" 587 确认端口可访问,然后发布中继服务提供的 SPF 和 DKIM 记录。如果运行的是 Cal.com,请检查是否已替换随附的 EMAIL_SERVER_HOST=localhostEMAIL_SERVER_PORT=1025 默认值;这些值指向本地开发邮箱。

自托管 Cal.com 是否支持与 CalDAV 同步,还是只能与 Google 同步?

两者都支持,但成熟度不同。CalDAV 应用标记为 beta,已通过 Baikal、Radicale、Nextcloud 和 Kerio Connect 等服务器的验证;Apple iCloud 也可通过它使用专用应用密码。Google Calendar 和 Microsoft 365 都支持双向同步,但在自托管安装中,您必须创建自己的 OAuth 客户端,并通过 GOOGLE_API_CREDENTIALS 提供该客户端,因为托管服务的凭据不在源代码中。

为什么我的 Google Calendar 同步在一周后停止工作?

因为 Google Cloud 项目仍处于 Testing 发布状态。Google 会向处于该状态的应用发放有效期为 7 天的刷新令牌,因此连接最初可以正常工作,但下一次刷新令牌时会失效,应用日志会显示 invalid_grant。将 OAuth 同意屏幕切换为 In production,然后重新连接一次日历。只重新连接而不更改状态,只能再延长 7 天。

哪些应用可以在 1 GB VPS 上运行?

Easy!Appointments 可以,因为它是 PHP 应用并使用 MySQL。Rallly 文档要求至少 2 GB,其捆绑的软件栈会运行 4 个服务。Cal.com 和 DayOtter 未公布最低配置,但 Next.js 应用需要 PostgreSQL;DayOtter 还需要 Redis 和 worker 进程,因此应规划 2 GB 或更多内存。不要在小型服务器上从源代码构建 Cal.com:项目自身的构建说明要求 Node 堆达到 16 GB,因此应改用预构建镜像。

#scheduling#calendly#cal-com#self-hosted#booking