가드레일 우선(Guardrails First): 스케줄링만 가능한 Make 및 Buffer 파이프라인
요약
Anthropic Claude를 에이전트로 활용하여 콘텐츠 기획, 이미지/영상 생성, 그리고 Make.com을 통해 Buffer에 게시물을 스케줄링하는 자동화 파이프라인 구축 과정을 설명합니다. 이 시스템은 여러 단계에서 엄격한 '가드레일'을 적용하여 오류와 잘못된 배포를 원천적으로 차단합니다.
핵심 포인트
- Claude 에이전트가 콘텐츠 기획 및 도구 호출을 담당함.
- Make.com 시나리오는 필터링과 조건 검사를 통해 스케줄링만 가능하도록 설계됨.
- 채널 ID 등은 정확한 매칭(Exact Match)을 요구하여 오류를 방지함.
- API 키는 Make의 키체인에 저장되어 보안성을 높임.
이번 달 어느 날 아침, 저는 게시 일정에 대해 문의했고 Facebook, Instagram, YouTube의 Buffer에 27개의 대기 중인 게시물을 확인했습니다. 제가 직접 작성한 것은 하나도 없었습니다. 이것은 그것이 작동하는 기술적인 측면이며, 대부분은 '아니오'라고 말하도록 구축된 부분들에 관한 것입니다.
구조:
Anthropic의 모델인 Claude가 에이전트 역할을 합니다. 이 모델은 규칙 파일과 사실 테이블로부터 매주 초안을 작성하고, 코드를 통해 이미지와 Shorts를 만들며, Make의 MCP 서버를 통해 세 가지 Make 시나리오를 도구로 호출합니다. 각 시나리오는 몇 가지 단순한 입력을 받아 Buffer에 하나의 게시물을 전달하며, Buffer는 이를 제 Facebook 페이지, Instagram, YouTube에 게시합니다.
이미지와 비디오는 제가 소유한 사이트에 저장됩니다. 왜냐하면 Buffer가 게시물이 올라갈 때 공개 링크에서 미디어를 가져오기 때문입니다. 각 파일은 게시물이 나갈 때까지 유지됩니다.
이 모든 것이 실행되기 전에, 주간 일정은 하나의 마크다운 파일로 저에게 옵니다. 제가 승인하기 전까지는 아무것도 대기열에 추가되지 않습니다.
가드레일 1: 시나리오는 스케줄링만 가능해야 함
모든 시나리오는 필터로 시작합니다. 세 가지 조건이 모두 통과해야 하며, 그렇지 않으면 한 작업에서 실행이 중단되고 아무것도 Buffer에 도달하지 않습니다.
{
"name": "알려진 채널, 최소 30분 후, video on merakislove.com",
"conditions": [[
...
'지금 게시(post now)' 경로가 없습니다. Buffer는 dueAt을 가진 mode: customScheduled를 받으며, 필터는 30분 미만의 시간을 거부하므로, 잘못된 시계나 잘못된 시간대가 즉시 게시로 이어질 수 없습니다.
가드레일 2: 호출자는 절대 ID를 제공해서는 안 됨
에이전트는 facebook 또는 instagram이라고 말합니다. Make는 이 단어를 자체적으로 Buffer의 채널 ID로 변환합니다.
{{switch(var.input.channel; "facebook"; "<facebook channel id>"; "instagram"; "<instagram channel id>")}}
다른 것은 비어 있게 되어 존재 확인에 실패합니다. 일치는 정확해야 하므로, 대문자 F가 붙은 Facebook도 거부됩니다. 저는 게시물이 잘못된 곳으로 가는 것보다 대문자를 수정하는 것을 선호합니다.
가드레일 3: 후행 슬래시
미디어 검사는 텍스트가 "https://merakislove.com/"으로 시작해야 하며, 끝의 슬래시(/)가 중요합니다. 이 슬래시가 없으면
Guardrail 4: 핵심은 Make에 있습니다
두 비디오 경로는 Make의 HTTP 모듈에서 Buffer의 GraphQL API를 호출하며, 인증에는 Make 키체인(keychain)에 저장된 API 키가 사용됩니다. 저는 Buffer에서 이 키를 생성한 후 직접 Make에 입력했습니다. 시나리오에서는 이를 키체인 참조로 언급하므로, 에이전트는 키가 프롬프트, 파일 또는 채팅에 나타나지 않더라도 경로를 실행하고 Buffer의 응답을 읽을 수 있습니다.
요청 자체는 하나의 Mutation입니다:
mutation Create($input: CreatePostInput!) {
createPost(input: $input) {
__typename
...
그리고 인스타그램 릴스(Instagram Reel)의 경우 다음과 같은 형태로 변수들이 구성됩니다:
{
"input": {
"channelId": "<위 스위치에서 가져옴>",
...
게시물 텍스트는 본문으로 들어가기 전에 Make의 JSON 트랜스포머(JSON transformer)를 거치므로, 따옴표, 백슬래시, 줄 바꿈이 요청을 깨뜨릴 수 없습니다. 저는 이 세 가지 경우 모두 테스트했습니다.
Make의 한 디테일 때문에 제가 실행을 실패한 적이 있습니다. Make의 API를 통해 생성된 HTTP 모듈(버전 4)에는 매퍼(mapper)에 stopOnHttpError, allowRedirects, shareCookies, requestCompressedContent가 설정되어야 합니다. 이것들을 빠뜨리면 "Validation failed for 4 parameter(s)."라는 오류와 함께 실행이 실패합니다.
펜스 테스트하기
저는 검사 과정을 직접 실패하는 것을 볼 때까지는 신뢰하지 않습니다.
사진 경로의 경우, Buffer 단계를 아무것도 하지 않는 단계로 대체하여 복사본을 실행했습니다. 두 개의 입력은 통과해야 했고 여섯 개는 거부되어야 했습니다: 유튜브 채널 이름, 대문자 F가 포함된 페이스북, 과거 날짜, 12분 후 시간, 다른 사이트의 이미지, 그리고 일반 http를 사용하는 이미지입니다. 두 개가 통과했습니다. 여섯 개는 한 작업에서 중단되었습니다.
Facebook 및 Instagram 동영상 경로의 경우, 테스트 복사본이 존재하지 않는 채널 ID를 가리켰습니다. 유효한 요청은 게시물을 생성할 필요 없이 Buffer의 검사를 통과할 수 있었습니다. Buffer는 NotFoundError를 응답했고, 이는 요청이 올바르게 구성되었음을 증명했습니다. 그런 다음 의도적으로 잘못된 게시 유형을 보내 스키마 오류가 반환되었고, 이는 Buffer 자체의 유효성 검사가 실행되고 있음을 증명했습니다. 두 복사본 모두 이후 삭제되었습니다.
미디어 구축하기
스크린샷이 깨진 페이지를 거부하는 방법
이미지는 Playwright와 Chromium을 통해 라이브 데모 페이지에서 가져옵니다. 헬퍼는 채팅 버튼과 플로팅 콜 바를 숨기고, CSS 선택자로 스크롤하며, 1600x900, 1200x675 또는 1080x1350 크기로 캡처합니다. 제 첫 번째 버전은 파일이 존재한다고 신뢰했습니다. 그러자 스크립트 청크가 실패하여 미리보기가 공백으로 반환되었고, 이틀 후에는 스타일시트가 도착하지 않아 가격 페이지가 스타일링되지 않은 텍스트로 반환되었습니다. 이제 그것은 실패를 감지하고 이미지를 거부합니다:
def missing(kind, u):
if kind == "stylesheet" or (kind in ("script", "font", "image") and urlparse(u).netloc == host):
lost.append(f"{kind} {u}")
...
또한 단색으로 나오는 이미지도 거부합니다. 스타일시트가 누락된 로컬 페이지에서 (거부)와 존재하는 경우 (작성됨)에 대한 가드레일을 증명했습니다.
한 데모는 다음 세금 마감일로 카운트다운을 하므로, 오늘 찍은 스크린샷은 게시되는 날짜에 잘못된 숫자를 보여줄 것입니다. 초기화 스크립트는 페이지의 시계를 게시 시간으로 이동시킵니다:
(() => {
const R = Date, off = new R("2026-10-15T22:10:00Z").getTime() - R.now();
class D extends R {
...
페이지 기록을 통한 쇼츠(Shorts)
동일한 브라우저가 휴대폰 크기(2.5배 기준 432x576)로 데모를 열고, 스크립트가 이징(easing)이 적용된 시간별 키프레임에 따라 스크롤하는 동안 DevTools 화면 녹화를 통해 프레임을 기록합니다. 저는 각 사이트의 고정 헤더(sticky header)를 측정하여 언급되는 라인이 그 아래로 절대 밀려나지 않도록 했습니다. ffmpeg은 녹화본을 제목, 자막, 배경 음악, 엔드 카드가 포함된 1080x1920 프레임에 배치합니다. Buffer는 세로가 가로보다 넓은 YouTube Short를 거부하는데, 이는 제가 첫 번째 16:9 광고 영상이 "YouTube Shorts의 경우 비디오는 수직(세로 방향)이어야 합니다."라는 메시지와 함께 돌아왔을 때 알게 된 것입니다.
제 목소리로 내레이션하기
매주 하나의 Short를 ElevenLabs의 음성 복제본이 내레이션합니다. 각 테이크마다 크레딧이 소모되므로, 모든 테이크는 재시도 루프 없이 한 번만 생성됩니다. 그런 다음 ffmpeg이 양쪽 끝의 침묵을 자르고, 포먼트(formants)를 유지하면서 러버밴드(rubberband)로 템포를 늦추고, loudnorm=I=-17:TP=-1.5:LRA=9를 사용하여 레벨링합니다. faster-whisper (small.en, 단어 타임스탬프 포함)가 다시 듣기를 하고, 각 자막은 해당 단어가 차지하는 구간에 맞춰 타이밍됩니다.
클링(Kling)과 만화
제 AI Cookbook에는 만화 오리 선생님이 생겼습니다. Kling의 이미지 모델은 2 크레딧으로 시작 프레임을 그리고, 비디오 모델은 프롬프트에 인용된 대사로 음성을 생성하여 100 크레딧으로 10초짜리 1080p 클립을 애니메이션화합니다. 후속 에피소드에서는 첫 번째 프레임을 캐릭터 레퍼런스로 사용하여 오리와 아이가 일관되게 유지됩니다. 에이전트는 소리를 들을 수 없기 때문에, 음성 말풍선을 추가하고 HTML로 제작된 엔드 카드를 추가하기 전에 각 클립을 전사(transcribe)하여 대사를 확인한 다음, 저에게 한 번 들어보라고 요청합니다. 모든 작업은 제가 미리 설정한 상한선 내에서 실행됩니다. 하나의 클립이 한 시간 이상 지연되면, 에이전트는 이를 보고하고 재제출하지 않았습니다.
첫 주에 문제가 된 것들
빈 화면의 스타일이 없는 스크린샷은 위 실패 리스너를 통해 수정되었습니다.
Shorts용으로 16:9 비디오가 거부되어 대신 세로형 클립이 게시되었습니다.
Buffer의 무료 플랜은 채널당 10개의 예약된 포스트를 지원합니다. 11번째 포스트는 '예약된 포스트 제한(You've hit the 10 scheduled post limit)'이라는 메시지와 함께 403 에러로 반환되었으며, 아무것도 생성되지 않았고 시나리오가 불완전한 실행 기록을 저장하지 않아 스스로 재시도하는 것도 없었습니다.
제 첫 Shorts는 제가 그것들을 확인하기도 전에 대기열에 들어갔습니다. 그래서 이제 승인 파일이 먼저 오게 되었습니다.
다이어그램과 각 펜스(fence) 뒤의 추론 과정이 포함된 전체 작성 글은 위에 링크된 표준 버전이며, 이 포스트들이 특징으로 하는 데모 사이트는 merakislove.com입니다. 만약 여러분이 Buffer의 제한이나 Make의 HTTP 특이점을 다른 방식으로 처리했다면 댓글로 알려주세요. 자료를 비교해 보고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기