인스타그램 카드뉴스 자동 발행 — n8n으로 HTML을 이미지로, 이미지를 피드로
이 글과 연결된 글 보기
드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.표시된 글 7편 목록
구글시트에 캡션과 이미지 폴더(또는 HTML 카드 내용)를 채워두면, n8n이 이미지를 만들거나 모아서 인스타그램에 카드뉴스로 자동 발행하는 워크플로우입니다. HTML을 이미지로 바꾸는 서비스 하나, Instagram Graph API 인증 하나만 준비하면 콘텐츠 발행 담당자가 매번 앱을 열어 사진을 올리고 캡션을 붙이던 작업이 시트 입력 한 줄로 줄어듭니다.
이 워크플로우 하나로 보여줄 수 있는 것
"콘텐츠 자동 발행"은 포트폴리오에서 꽤 자주 등장하는 문구지만, 그 안을 뜯어보면 성격이 다른 역량 세 가지가 들어 있습니다.
- HTML을 이미지로 바꾸는 외부 API 호출 — 디자인 툴 없이 코드로 만든 카드를 이미지 파일로 변환하는 경험
- 여러 장을 순서대로 만드는 반복 구조 — 캐러셀처럼 "N개를 만들어서 하나로 묶는" 흐름을 설계하는 경험
- 비동기 API의 2단계 호출 패턴 — 결과물을 만드는 요청과 그걸 실제로 게시하는 요청이 분리된 API를 다루는 경험
세 번째가 가장 오래 남는 배움입니다. Instagram Graph API는 미디어를 컨테이너로 먼저 만들고, 그 컨테이너가 준비됐는지 확인한 뒤에야 발행할 수 있습니다. 이런 "만들기 요청과 확정 요청이 분리된" 구조는 결제 API, 영상 인코딩 API에서도 똑같이 나타나므로, 한 번 다뤄보면 다른 비동기 API를 만났을 때 낯설지 않습니다.
배경 — 카드뉴스 한 장을 올리는 데 드는 반복 작업
인스타그램 카드뉴스는 이미지를 여러 장 만들고, 캡션을 쓰고, 순서를 맞춰 캐러셀로 올리는 작업입니다. 콘텐츠가 정기적으로 나가는 계정이라면 이 과정이 매번 똑같이 반복됩니다. 데이터팝콘 n8n 인스턴스에는 이 반복을 자동화한 워크플로우가 브랜드별로 두 개 있습니다. 하나는 미리 만들어 둔 이미지를 모아 캐러셀로 올리고, 다른 하나는 시트 데이터로 HTML 카드를 직접 조립해 이미지로 바꿔서 올립니다. 이 글에서는 두 워크플로우를 함께 뜯어보면서 "같은 목적, 다른 이미지 소스"라는 관점으로 정리합니다. 설치나 기본 노드 설명은 n8n으로 반복업무 자동화 포트폴리오 만들기에 정리돼 있으니, 이 글에서는 워크플로우 자체만 다룹니다.
시작하기 전에 준비할 게 두 가지 있습니다. 인스타그램 발행에는 Meta 개발자 앱과 연결된 인스타그램 전문 계정(비즈니스 또는 크리에이터)이 필요하고, HTML을 이미지로 바꾸는 데는 hcti.io 같은 서비스 계정이 필요합니다. hcti.io는 공식 요금제 페이지 기준(2026년 8월) 월 50장까지 무료이고, 이후 1,000장에 14달러부터 시작합니다. 카드뉴스 몇 장을 테스트하는 정도면 무료 티어로 충분합니다.
구조 — Trigger / Task / Target
- Trigger — 정해진 시각의 스케줄
- Task — 시트에서 오늘 올릴 콘텐츠를 찾고, 이미지를 준비(다운로드+업로드 또는 HTML 변환)한 뒤 인스타그램 미디어 컨테이너를 만들어 발행
- Target — 인스타그램 피드, 그리고 처리 완료를 표시하는 구글시트
만들기
1. 시트를 상태 머신으로 쓰기
두 워크플로우 모두 구글시트를 "발행 대기열"로 씁니다. Status 컬럼이 ToDo인 행 중 Scheduled_at이 오늘 날짜인 것만 골라 처리하고, 발행이 끝나면 같은 행을 Processed로 바꿔 씁니다. Google Sheets 노드의 필터 조건은 이렇습니다.
Status=ToDoScheduled_at={{ $now.toFormat('yyyy-MM-dd') }}
이 구조 덕분에 스케줄이 매일 같은 시각에 돌아도 이미 처리한 콘텐츠를 다시 올리는 일이 없습니다. 발행 뒤에는 Update 노드가 Folder(또는 날짜) 컬럼을 매칭 기준으로 삼아 같은 행의 Status를 Processed로, Processed_at을 현재 시각으로 채웁니다.
2. 이미지 준비 — 두 가지 방식
여기서부터 두 워크플로우가 갈라집니다.
방식 A. 미리 만든 이미지를 모으기. Google Drive 노드로 시트에 적힌 폴더 안 이미지 목록을 가져오고, 파일명 기준으로 정렬한 뒤 하나씩 다운로드합니다. 인스타그램이 요구하는 공개 URL을 만들기 위해 각 이미지를 Cloudinary API(멀티파트 업로드, 업로드 프리셋 방식)에 올려 URL을 받습니다. Drive 다운로드 링크를 그대로 쓰지 않는 이유는, 인스타그램 서버가 로그인 없이 접근할 수 있는 공개 URL이어야 하기 때문입니다.
방식 B. Code 노드로 HTML을 조립해 이미지로 바꾸기. 시트에서 읽은 행 데이터를 문자열 템플릿으로 엮어 HTML 문서 하나를 만듭니다. 원본 Code 노드에는 이미 처리한 콘텐츠를 다시 만들지 않도록 막는 가드가 들어 있습니다.
// 이미 업로드된 콘텐츠면 여기서 종료 — 다음 노드 실행 안 됨
if (isAlreadyUploaded) {
return [];
}
이렇게 만든 HTML은 HTTP Request 노드로 hcti.io에 보내 이미지로 변환합니다.
// hcti.io HTTP Request 노드 Body
{
html: "={{ $json.html_content }}",
viewport_width: "1080",
viewport_height: "1350",
width: "100"
}
인증은 hcti.io가 발급하는 API Key/Secret을 Basic Auth credential로 등록해 씁니다. 응답으로 받는 이미지 URL이 그대로 다음 단계의 image_url이 됩니다. 뷰포트를 1080×1350으로 잡은 건 인스타그램 세로형 이미지 비율(4:5)에 맞춘 값입니다.
3. 인스타그램 토큰 유효성 체크
두 워크플로우 모두 발행 전에 /me 엔드포인트를 access_token과 함께 호출해 응답에 id가 들어 있는지 확인합니다. 비어 있으면 Stop and Error 노드로 "Invalid Access Token" 에러를 내고 멈춥니다. 다만 이 체크가 놓인 자리가 다릅니다. 한쪽은 매일 도는 본 흐름 중간에 그대로 끼워 넣어 매번 자동으로 확인하고, 다른 한쪽은 별도의 Manual Trigger로 시작하는 흐름으로 분리해 토큰을 새로 발급했을 때만 수동으로 실행합니다.
4. 미디어 컨테이너 생성과 발행 — 두 번의 API 호출
인스타그램에 이미지를 올리는 API는 한 번에 끝나지 않습니다. 먼저 POST /{ig-user-id}/media로 이미지 URL과 캡션을 보내 미디어 컨테이너를 만들고, 응답으로 받은 creation_id를 POST /{ig-user-id}/media_publish에 넘겨야 실제로 게시됩니다. 원본 워크플로우는 두 호출 사이에 15초 또는 60초짜리 Wait 노드를 하나 끼워 컨테이너가 준비될 시간을 줍니다.
캐러셀(여러 장)을 올릴 때는 이 과정이 한 겹 더 있습니다. 이미지마다 먼저 낱장 컨테이너를 만들고, Aggregate 노드로 각 컨테이너의 id를 하나의 목록으로 모읍니다. 그다음 media_type=CAROUSEL, children에 그 id 목록을 담아 캐러셀 컨테이너를 새로 만들고, 이 컨테이너의 creation_id를 media_publish에 보내야 캐러셀 전체가 한 게시물로 올라갑니다.
POST /{ig-user-id}/media
media_type=CAROUSEL
children={{ 낱장 컨테이너 id 목록 }}
caption={{ $json.Caption }}
이 워크플로우는 graph.instagram.com 엔드포인트를 씁니다. 이건 "Instagram Login을 이용한 Instagram API" 경로로, Meta 공식 문서에 따르면 인스타그램 비즈니스·크리에이터 계정과 Meta 개발자 앱만 있으면 되고 페이스북 페이지 연결은 요구하지 않습니다. 페이스북 페이지가 필요한 쪽은 graph.facebook.com을 쓰는 옛 방식(Facebook Login for Business)이라, 지금 새로 시작한다면 이 워크플로우처럼 인스타그램 계정 기준으로 인증하는 경로가 더 간단합니다.
5. 액세스 토큰은 credential로
원본 워크플로우에는 액세스 토큰과 계정 ID가 Set 노드 안에 문자열로 직접 적혀 있습니다. 이렇게 두면 워크플로우를 export할 때 값이 파일에 그대로 남습니다. 새로 만든다면 Set 노드 대신 n8n credential(HTTP Header Auth 등)로 토큰을 등록하고, HTTP Request 노드에서 credential만 선택하는 편이 안전합니다.
원본 캔버스에 남아 있는 흔적
두 워크플로우는 같은 구글시트 문서를 참조하고 있습니다. 브랜드는 다른데 시트는 하나를 공유한다는 건, 처음 만든 워크플로우를 복제해 브랜드별로 갈라 썼다는 뜻입니다. 그래서 두 캔버스의 노드 이름과 구조가 거의 똑같고, 이미지를 준비하는 단계만 다릅니다.
HTML 변환 쪽 캔버스에는 스티키 노트로 "릴스", "스토리", "이미지" 세 구획이 나뉘어 있고, "에셋(이미지, 영상) 불러오기 — 구글 드라이브(클라우드 저장소) → 깃허브(용량이 적기도 하고 공유가 간편해서)"라는 메모가 붙어 있습니다. 실제로 캔버스에는 릴스·스토리 발행용 노드도 있지만 본 흐름에 연결돼 있지는 않습니다. 이미지 카드뉴스 다음으로 영상 콘텐츠까지 확장하려던 흔적으로 보이는데, 아직 이미지 발행 흐름만 실제로 돌고 있습니다.
한계
- 컨테이너 상태를 확인하지 않는다 — 두 워크플로우 모두 고정된 Wait(15초·60초)만 두고 바로 발행을 시도합니다. Meta는 컨테이너의
status_code가FINISHED인지 1분 간격으로 최대 5분까지 확인할 것을 권장합니다. 이미지가 크거나 서버가 느리면 Wait 시간 안에 준비가 안 끝나 발행이 실패할 수 있습니다. - 실패했을 때 재시도나 알림이 없다 — Stop and Error는 토큰이 비어 있을 때만 걸리고, 컨테이너 생성이나 발행 호출이 실패해도 뒤 노드가 조용히 멈출 뿐입니다.
- 캐러셀 순서가 파일명에 의존한다 — Drive 폴더의 이미지를 이름순으로 정렬해 슬라이드 순서를 정하므로, 파일명 규칙이 어긋나면 카드 순서도 어긋납니다.
- 토큰 만료 대응이 수동이다 — 액세스 토큰이 만료되면 Stop and Error로 멈추기만 하고, 재발급은 사람이 직접 해야 합니다.
포트폴리오·면접 포인트
포트폴리오에는 이 워크플로우를 그대로 복제하기보다, 두 이미지 준비 방식 중 하나를 골라 자기 콘텐츠로 바꿔보는 걸 권합니다. HTML 카드 템플릿을 직접 디자인해서 hcti.io로 변환하는 쪽이 "코드로 디자인을 만들었다"는 이야기가 되어 더 구체적입니다.
면접에서 "왜 API를 한 번에 안 부르고 컨테이너 생성과 발행을 나눴나요?"라는 질문이 오면, 인스타그램 쪽에서 이미지 처리가 비동기로 이뤄지기 때문에 컨테이너가 준비된 뒤에야 발행이 가능하다는 점, 그리고 이 워크플로우는 고정 대기 시간으로 그 처리를 기다리지만 정확히 하려면 컨테이너 상태를 조회해야 한다는 점까지 답하면 API 구조를 이해하고 있다는 인상을 줍니다.
다음 액션
이전 편에서는 iOS 단축어로 시리에게 말을 걸면 n8n 에이전트가 답하는 음성 인터페이스를 다뤘습니다. 이번 편은 그 반대로, 사람이 개입하지 않아도 콘텐츠가 스스로 나가는 흐름이었습니다. 다음 편에서는 제품 이미지 한 장을 영상으로 바꾸는 워크플로우로 넘어갑니다. 카드뉴스가 "정적인 이미지를 자동으로 만든다"였다면, 다음은 "정적인 이미지를 움직이는 영상으로 만든다"로 한 단계 더 나아갑니다.
