自架 Calendly 替代方案比較:同步與寄信
比較 Cal.com、Easy!Appointments、Rallly 與 DayOtter 的 VPS 部署,聚焦雙向行事曆同步與外寄電子郵件,並說明 port 25 受封鎖時為何必須使用 relay。
簡短結論
自架的 Calendly 替代方案必須做到 VPS 上的內部工具從未需要做到的事:服務公眾。預約頁面本身就是產品。從第一天起就需要真正的網域名稱與 TLS(傳輸層安全性),而且必須能將郵件寄送給從未聽過你伺服器的人。
有四個專案符合實際需求。Cal.com 最接近 Calendly,是個人顧問的預設選擇。Easy!Appointments 輕量,採用 PHP 和 MySQL,在 1 GB VPS 上也能順利運作。Rallly 是群組投票工具,完全沒有預約頁面。DayOtter 是最新加入者,採用 AGPLv3 授權的排程平台,前端提供先確認的助理。
有兩個問題決定你實際能執行哪一個。它能否與你目前使用的行事曆進行雙向同步?它能否寄送郵件?第二個問題是大多數自架預約系統暗中失敗的地方,因此先從這裡開始。
外寄電子郵件是最容易失敗的環節
預約確認信可能會寄到陌生人的收件匣。這類交易型郵件會送達 Gmail 或 Microsoft 365,而這些收件服務會根據寄件 IP 位址與 DNS 記錄評估你的郵件。
直接從 VPS 寄信幾乎不會成功。多數供應商會封鎖新帳號的對外 TCP port 25,因此連線會掛起,最後逾時。即使 port 25 未被封鎖,新 VPS 位址也沒有寄信紀錄,大型收件服務通常會將來源不明的主機代管網段位址視為可疑。預約已寫入資料庫,頁面也顯示已確認,但沒有人會收到郵件。從伺服器端看不出任何錯誤,因此通常要到數週後,客戶未出席時才發現問題。
請使用 relay。任何交易型郵件供應商都可以使用,而應用程式只需要 hostname、port、使用者名稱與密碼。在修改應用程式設定前,先確認該 port 可連線:
nc -vz -w 5 "$SMTP_HOST" 587出現 succeeded 行表示路徑已開放。連線掛起或出現 Connection refused 表示該 port 在網路層遭到封鎖,修改多少次 .env 都無法解決。Relay 使用 587 或 465 接聽,正是因為 port 25 經常遭到封鎖。
每個專案使用 relay 的方式不同。Cal.com 讀取 EMAIL_FROM、EMAIL_SERVER_HOST、EMAIL_SERVER_PORT、EMAIL_SERVER_USER 與 EMAIL_SERVER_PASSWORD,也接受 RESEND_API_KEY。請特別注意:隨附的 .env.example 會將 EMAIL_SERVER_HOST 指向 port 1025 上的 localhost,這是本機開發用信箱。保留預設值時,應用程式會將郵件送入不存在的目的地,而且不會顯示錯誤。Rallly 使用 SMTP_HOST、SMTP_PORT、SMTP_USER 與 SMTP_PWD。DayOtter 可使用 SMTP 設定或 Resend key。Easy!Appointments 會從應用程式本身寄送通知,因此在接受實際預約前,請先從設定頁面將它指向相同的 relay。
接著發布 relay 提供的 DNS 記錄。SPF(sender policy framework)記錄會指出哪些伺服器可以代表你的網域寄信;DKIM(domainkeys identified mail)key 會為每封郵件簽章,讓收件服務確認郵件未遭竄改。兩者都通過後,再加入 DMARC(domain-based message authentication, reporting and conformance)政策。將測試預約寄到大型供應商的實際地址,開啟郵件標頭,確認驗證行顯示 pass。無法寄送郵件的預約頁面比沒有預約頁面更糟,因為它會靜默失敗。
哪些行事曆後端真正支援雙向同步
同步分為兩個方向,而且兩者可能分別失敗。讀取方向負責查詢可用時段:應用程式必須看得到你現有的忙碌時段,否則可能會提供你已經有安排的時段。寫入方向則負責建立預約:已確認的事件必須出現在你實際查看的行事曆中,而不只是留在預約工具內。
Google Calendar 和 Microsoft 365 都支援雙向同步,但自架安裝有一項條件。你必須自行建立 OAuth(開放授權)用戶端,因為代管產品的 client ID 不在原始碼中。對 Cal.com 而言,GOOGLE_API_CREDENTIALS 位於 .env,其中存放從 Google Cloud 主控台下載的 JSON。DayOtter 也以相同方式使用 Google 和 Microsoft OAuth 憑證。
這裡有兩個問題可能造成同步失敗,開始設定前值得先了解。第一,你註冊的 redirect URI 必須與公開 URL 完全相同,包括 scheme 和結尾路徑,否則 Google 會在同意畫面顯示 redirect_uri_mismatch 並停止連線。第二,發布狀態仍為 Testing 的 Google 專案,其 refresh token 會在 7 天後過期。同步可能整週正常運作,之後停止,而應用程式日誌會在下一次重新整理時顯示 invalid_grant。請將同意畫面移至 In production,或接受每週一手動重新連線。
CalDAV(WebDAV 的行事曆擴充功能)是開放式選項,但支援較有限。Cal.com 內建的 CalDAV app 目前仍標示為 beta,並已針對 Baikal、Radicale、Nextcloud 和 Kerio Connect 等伺服器完成驗證。Apple iCloud 也能透過相同的 app 使用,但需要 app-specific password,而不是 Apple ID password。DayOtter 將 Apple 透過 CalDAV 列在 Google 和 Microsoft 365 旁邊。
ICS feed 並不是同步。訂閱的 .ics URL 依設計只能讀取,因此可以在預約頁面封鎖忙碌時段,但永遠無法接收預約。如果工具只提供行事曆的 ICS,你只完成了一半的整合,仍然必須手動複製事件。
Easy!Appointments 只能同步 Google Calendar,沒有其他選項。Rallly 完全不會讀取可用時段,而是針對一組候選日期收集投票。它適合回答「我們 6 個人什麼時候可以見面」,不適合用來「預約和我 30 分鐘的時段」。
預約頁面是公開服務,因此 TLS 必須優先處理
大多數人自行代管的服務都是私有的。Wiki、看板和儀表板都可以放在 VPN 或 SSO 登入後方,完全不暴露在公開網際網路上。但預約連結無法這樣處理。任何收到連結的人都必須載入頁面,因此設定方式會有 3 個具體差異。
在安裝任何元件前,您需要一個 A record 指向 VPS 的網域名稱。第一天就需要憑證,因為瀏覽器會將一般 HTTP 表單標示為不安全,而客戶會在表單中輸入姓名和電子郵件地址。此外,您必須在應用程式設定中正確設定公開 URL,因為這個值會寫入寄出電子郵件中的連結,以及 OAuth redirect 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。
Cal.com 在 VPS 上
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 heap。在 ARM 硬體上,請在映像標籤加上 -arm 尾碼。專案沒有公布執行預先建置映像檔的最低需求,因此請將 2 GB 視為我對應用程式加 PostgreSQL 的實務估計,而不是官方文件中的數值,並在第一週監控記憶體使用量。
確認服務已啟動:
docker compose ps
docker compose logs -f calcom
curl -sI https://cal.example.com | head -n 1curl 應輸出 HTTP/2 200。如果容器顯示正在執行,但 nginx 回傳 502 Bad Gateway,通常表示首次啟動仍在套用資料庫 migration。請等待幾分鐘並查看日誌,再判定服務是否故障。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/mysqlBASE_URL 必須是公開的 HTTPS 位址。若設定錯誤,確認電子郵件中的預約連結會指向客戶無法連線的主機。此映像檔會在 port 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將任何 script 傳送至 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_PASSWORD、SUPPORT_EMAIL 和 INITIAL_ADMIN_EMAIL。如果你已經執行反向代理,請設定 PROXY_MODE=external 和 WEB_PORT,Traefik 就不會介入。如果你已經使用 自架且與 S3 相容的 MinIO 物件儲存, 請將 S3_* 變數指向該服務,並移除 Garage 容器。
這裡不能省略 SMTP,因為登入方式是 magic link。沒有可正常運作的 relay,任何人都無法登入,包括你剛建立的管理員帳號。這是較理想的電子郵件失敗情況:它會在入口阻止你,而不是讓你在 3 週後才發現客戶的預約已經遺失。
DayOtter,最新加入者
DayOtter 是採用 AGPLv3 的排程平台,並附帶助理功能。正式環境只需執行一個命令:
curl -fsSL https://raw.githubusercontent.com/Dayotter/dayotter/main/deploy/install.sh \
| sudo DAYOTTER_DOMAIN=cal.example.com bash如上所述,執行前請先閱讀該命令。安裝程式會設定 Docker、產生 secrets,並啟動完整堆疊:Next.js web app、處理提醒、行事曆同步與 webhooks 的背景 worker、PostgreSQL、Redis,以及具備自動 HTTPS 功能的 Caddy。
四個平台中,DayOtter 支援的行事曆最廣。它支援 Google、Microsoft 365、透過 CalDAV 的 Apple,以及 ICS feeds;ICS 的限制如上所述。其他整合功能都必須透過環境變數選擇性啟用,包括用於寄信的 SMTP 或 Resend、用於助理的 ANTHROPIC_API_KEY、用於 SMS 的 Twilio,以及用於付款的 Stripe。助理採用先確認機制:它先提出建議,經你核准後才會執行;未取得明確同意前,不會將任何內容寫入行事曆。若將 API key 留空,這部分功能便不會執行。
對自行代管者而言,授權條款很清楚。核心採用 AGPLv3,ee/ 目錄則包含僅限商用雲端的商業授權;只有設定 DAYOTTER_CLOUD=1 時,該授權才會生效。這表示截至 August 2026,代管方案每個 seat 每月收費 $9 的團隊功能,也能在你自己的伺服器上使用。
這也是本篇平台中堆疊最龐大、專案年資最短的一個。請讓它與現有的預約連結並行運作兩週,透過兩者接收實際預約,並在將客戶移轉過去前閱讀 worker logs。
各個堆疊實際需要的資源
容器數量是評估一組堆疊會向小型 VPS 索取多少資源的可靠指標,因為每項服務都有自己的最低記憶體需求。以下數量取自各專案自行發布的 Docker 堆疊,資料擷取時間為 August 2026。
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 指向自己的 proxy 和 object storage 後,可以移除其 Traefik 與 Garage 容器。Cal.com 的 Prisma Studio 是開發工具,不應在公開伺服器上持續執行。
應選哪個自架 Calendly 替代方案
個人顧問應使用 Cal.com。 在這些專案中,只有它同時提供使用者熟悉的預約頁面、可省去 VPS 上 Node 建置工作的預先建置映像檔,以及適合不使用 Google 或 Microsoft 行事曆使用者的 CalDAV 支援。使用一個 PostgreSQL 資料庫和一個應用程式容器,維護負擔足以長期承擔。請預留一個下午設定 OAuth client 和 mail relay。另請注意,CalDAV 應用程式仍處於 beta 階段,因此公開連結前,應先完整測試一次實際預約流程。
小型團隊應考慮 DayOtter。 加權循環分配和共同預約都包含在 AGPLv3 核心中,因此自架即可取得託管方案按座位收費的功能;worker process 也針對團隊實際依賴的提醒和 webhooks 而設計。取捨在於成熟度:它是這份清單中最新的專案,因此應先平行執行,並持續保留舊連結,直到觀察完整一個月的預約情況。
另外有兩種較特殊的情境。如果你只需要透過投票找出團體可以聚會的時間,請安裝 Rallly,做到這裡即可。如果你使用 1 GB VPS、日常使用 Google Calendar,並且只想要能接受預約的最精簡方案,Easy!Appointments 的壽命會比你能安裝在該主機上的任何複雜方案更長。至於同一台伺服器還值得自架哪些服務,請參閱 2026 年值得自架的服務。
FAQ
我可以在沒有網域名稱的情況下執行自架預約頁面嗎?
不行。這些應用程式都會將公開 URL 寫入確認電子郵件中的連結,而 Google 和 Microsoft 也會將 OAuth redirect URI 與相同的值比對,因此使用裸 IP 位址會在同意畫面顯示 redirect_uri_mismatch。Let’s Encrypt 也不會核發 IP 位址的憑證,因此頁面會以純 HTTP 載入,瀏覽器會將表單標示為不安全。請先購買網域,將 A record 指向 VPS,再進行安裝。
為什麼我的預約確認電子郵件始終收不到?
幾乎總是因為伺服器嘗試自行寄送郵件。多數 VPS 供應商會封鎖新帳戶的對外 port 25,因此連線會停滯;即使該連接埠未遭封鎖,新位址也沒有寄件信譽,大型郵件接收服務仍會拒收。請將應用程式指向 port 587 上的交易型郵件 relay,使用 nc -vz -w 5 "$SMTP_HOST" 587 確認該連接埠可連線,然後發布 relay 提供的 SPF 和 DKIM records。若您執行 Cal.com,請確認已替換隨附的 EMAIL_SERVER_HOST=localhost 和 EMAIL_SERVER_PORT=1025 預設值,因為它們會指向本機開發用信箱。
自架 Cal.com 能與 CalDAV 同步嗎?還是只能與 Google 同步?
兩者都可以,但成熟度不同。CalDAV 應用程式標示為 beta,並已針對 Baikal、Radicale、Nextcloud 和 Kerio Connect 等伺服器完成驗證;Apple iCloud 也能透過它搭配 app-specific password 使用。Google Calendar 和 Microsoft 365 都支援雙向同步,但在自架安裝中,您必須自行建立 OAuth client,並透過 GOOGLE_API_CREDENTIALS 提供該 client,因為託管服務的 credentials 不在原始碼中。
為什麼我的 Google Calendar 同步在一週後停止運作?
因為 Google Cloud project 仍處於 Testing 發布狀態。Google 會為處於該狀態的應用程式核發 7 天後到期的 refresh token,因此連線起初能正常運作,接著會在下一次 token refresh 時失效,而應用程式日誌會顯示 invalid_grant。請將 OAuth consent screen 移至 In production,再重新連線一次行事曆。若不變更狀態就重新連線,只能再延長 7 天,之後仍會失效。
哪些應用程式能在 1 GB VPS 上執行?
Easy!Appointments 可以,因為它是 PHP 應用程式,搭配 MySQL 即可。Rallly 文件列出的最低需求為 2 GB,而且其隨附的 stack 會執行 4 個服務。Cal.com 和 DayOtter 未公布最低需求,但 Next.js 應用程式搭配 PostgreSQL;DayOtter 另外還需要 Redis 和 worker process,因此您應規劃 2 GB 以上的記憶體。不要在小型主機上從原始碼建置 Cal.com:專案本身的建置指示要求 16 GB 的 Node heap,因此請改用預先建置的 image。