+ Pages Functions
자동매매 봇이 아니라 웹 제작 건입니다. 김치프리미엄 차익거래 프로그램을 파는 분이 그 프로그램 소개 페이지와 문의 접수창을 원했습니다. 서버도 DB도 없이 Cloudflare Pages 위에 올려 월 운영비 0원, 문의는 텔레그램으로 바로 갑니다. 2026년 8월 5일 정식 오픈. 작은 건인데 배포에서만 함정을 네 개 밟았습니다.
의뢰인은 이미 AWS Lightsail 인스턴스를 갖고 있었습니다. 그런데도 거기 올리지 않았습니다. 그 인스턴스가 거래소 API 키를 가진 실거래 시스템이라 공개 웹과 공격면을 같이 쓰게 할 수 없었습니다. 그리고 정적 페이지 + 함수 하나짜리 요구에 VPS를 붙이면 인증서 갱신과 OS 패치가 무유지보수 단발 건의 상시 부채가 됩니다.
Promise.allSettled로 동시 발송하고 한 곳이라도 성공하면 접수 성공. 수신자 한 명이 봇을 차단해도 문의가 유실되지 않습니다.// functions/api/inquiry.js — 접수 → 텔레그램, 골자만
export async function onRequestPost({ request, env }) {
const f = await request.formData();
if (f.get("website")) return ok(); // 허니팟: 성공 위장 폐기
if (Date.now() - +f.get("t") < 3000) return reject(); // 타임트랩
const text = escapeHtml(`[문의] ${f.get("name")} / ${f.get("contact")}\n${f.get("msg")}`);
const sends = env.CHAT_IDS.split(",").map(id => sendTelegram(env.BOT_TOKEN, id, text));
const r = await Promise.allSettled(sends);
return r.some(x => x.status === "fulfilled") ? ok() : fail();
}
wrangler pages deploy는 functions 폴더를 현재 작업 디렉토리 기준으로 찾습니다. 루트에서 deploy site를 하면 site/functions는 무시되고 정적 파일만 올라갑니다. 배포 출력에 "Compiled Worker successfully"와 "Uploading Functions bundle" 두 줄이 없으면 함수가 빠진 겁니다.wrangler whoami로 대상 계정을 먼저 확인합니다.
수신자 chat_id를 잡으려는데 getUpdates가 계속 0건이었습니다. 텔레그램 봇은 상대가 /start를 누르기 전에는 먼저 메시지를 보낼 수 없습니다. @아이디만 알아도 소용없습니다.
그래서 의뢰인에게 "텔레그램 아이디 알려 주세요"가 아니라 "이 링크 눌러서 시작 버튼 눌러 주세요"로 요청해야 하고, 봇 소유권은 이전이 안 되니 처음부터 최종 운영자 계정으로 만들어야 합니다.
수신 가능 여부는 sendMessage를 직접 쏴서 확인했습니다.
"신청현황" 영역이 중앙정렬 → 12건 마퀴 → 분산배치 → 알약 카드로 네 번 바뀌었습니다. "휑하다", "정신없다" 같은 피드백을 시각 자료 없이 주고받은 탓입니다. 이후로는 디자인 방향을 2~3안 시안으로 먼저 고르게 하고, 그다음 세부 조정으로 단계를 나눕니다. 수정 횟수 상한이 있는 계약에서 이런 왕복은 곧 마진이라서요.
범위 밖 요구(회원가입·이용권·자동 쪽지)는 "별도입니다"가 아니라 "150~250만 원 규모입니다"로 답했습니다. 금액이 붙는 순간 의뢰인이 스스로 우선순위를 정리해 1단계만 선택했습니다. 반대로 링크가 헛도는 것 같은 하자는 무료 범위입니다. 그 경계를 문서로 나눠 두었습니다.
프로그램을 만들었으면 팔 자리도 필요합니다. 랜딩 + 문의 접수는 서버 없이 만듭니다.
알고랩이 24시간 빠르게 답변드립니다.