본문 바로가기
최신 IT

n8n + Google Sheets로 LinkedIn 포스팅을 자동화한 방법

by 구라100단 2026. 3. 10.

나는 짧은 내용을 LinkedIn에 올리고 싶었다. 마케팅 목적이 아니라, 우리가 무엇을 만들고 있는지를 공개적으로 기록하는 용도였다. 

포스팅을 매번 수동으로 하는 건 확장이 불가능하고, 예측 가능한 방식으로 실패한다. 포스팅이 늦어지고, 맥락이 사라지고, 업데이트 사이에 몇 주가 흘러가 버린다.

이상적인 해결책은 작성된 업데이트를 받아서 지속적인 관리 없이 일정대로 게시하는 가벼운 시스템이다. 또한 감사(audit)가 가능해야 한다 — 즉, 시스템의 상태를 들여다보고, 동작 방식을 파악하고, 로그나 대시보드를 뒤지지 않고도 오류를 확인할 수 있어야 한다.

이 글은 그 결과물인 n8n과 구글 시트로 구축한 링크드인 포스팅 파이프라인에 대한 기록이다. 이 시스템은 대기열(Queue)에서 글을 가져와 예약된 시간에 게시하고, 결과를 명시적으로 기록한다.

1. n8n에 대한 간단한 소개

n8n을 사용해 보지 않았다면, 이건 자동화를 위한 '시각적 워크플로우 빌더'라고 생각하면 된다. '노드(node)'들을 연결해서 워크플로우를 만든다. 각 노드는 워크플로우를 시작시키거나(트리거), 데이터를 변환하거나, 구글 시트, 슬랙, API 같은 외부 시스템과 연결하는 역할을 한다.

실제로는 다음과 같이 작동한다:

  1. 워크플로우: 스케줄이나 웹훅에 의해 실행된다.
  2. 데이터 추출: 시트, 데이터베이스, API 등에서 데이터를 가져온다.
  3. 로직 수행: 필터링, 분기, 재시도 등을 처리한다.
  4. 결과 전송: 링크드인, 이메일, CRM 등으로 데이터를 보낸다.

n8n은 시각적이면서도 프로그래밍이 가능해서 특히 효과적이다. 로우코드(low-code) 노드와 짧은 자바스크립트 스니펫을 결합해 더 세밀하게 제어할 수 있기 때문이다.

2. 우리가 만들 것

목표는 간단하다. 구글 시트에 쌓인 포스팅 대기열을 가져와 링크드인에 자동으로 게시하는 것이다. 이 솔루션은 견고한 시스템처럼 작동해야 한다. 예약 처리를 하고, 중복 게시를 방지하며, 시스템 상태를 명확히 알 수 있도록 결과를 기록해야 한다.

워크플로우의 동작 순서: 구글 시트에서 행을 읽고 -> 게시 준비가 된 것을 필터링하고 -> 가장 이른 예약 게시물을 선택해 -> 내 링크드인 프로필에 게시한 후 -> 결과를 다시 시트에 쓴다. (성공 시 'posted', 실패 시 타임스탬프와 에러 메시지와 함께 'failed' 표시)

3. 왜 구글 시트를 '대기열'로 썼는가?

에어테이블(Airtable), 데이터베이스, 노션 등을 쓸 수도 있었지만, 구글 시트는 어디서나 편집할 수 있고 기록 추적이 명확한 가벼운 대기열을 제공한다. 휴대폰으로도 포스팅 예약을 할 수 있고, 로그를 뒤지지 않고도 실패 원인을 디버깅할 수 있다.

시트를 보고 바로 알고 싶은 것들:

  • 나중에 올라갈 포스트는 무엇인가?
  • 이미 게시된 것은 무엇인가?
  • 실패해서 확인이 필요한 것은 무엇인가?

구글 시트가 완벽한 대기열은 아닐지 몰라도, 내 목적에는 가장 실용적이다. 링크드인 포스팅 같은 일에는 실용성이 이긴다.

4. 구글 시트 구조 (Schema)

LinkedInQueue라는 이름의 시트를 만들고 다음 열(Column)들을 추가했다

컬럼 설명
id 각 게시물의 고유 식별자
post_text LinkedIn에 실제로 게시될 내용
status queued | posting | posted | failed | skipped 중 하나
scheduled_at 게시 예정 날짜
posted_at 성공 후 기록되는 타임스탬프
linkedin_ref LinkedIn 응답에서 받은 ID (선택 사항)
error 게시 실패 시 오류 상세 내용

부연 설명: 최소한 status, scheduled_at, post_text는 꼭 있어야 시스템이 제대로 돌아간다.

5. 워크플로우 전체 흐름

n8n에서 이 워크플로우는 몇 개의 연결된 노드로만 이루어져 있다. 중요한 건 노드의 개수가 아니라, 제어 흐름이다.

시작은 몇 분마다 실행되는 스케줄 트리거다. 그다음 Google Sheets에서 모든 행을 읽고, 작은 "Code" 노드로 다음 게시물을 선택하고, 선택된 행을 잠가서 이중 게시를 방지하고, LinkedIn 노드로 게시하고, 마지막으로 성공/실패 상태를 시트에 업데이트한다.

이것이 프로덕션 수준 시스템에서 원하는 핵심 속성들을 제공한다:

  • 워크플로우가 일관되게 실행된다
  • 각 실행마다 최대 하나의 게시물만 발행된다
  • 상태가 Sheets에 저장되어 언제든 확인할 수 있다
  • 실패가 기록되고 복구 가능하다

Step 1: 스케줄 트리거

첫 번째 노드는 "Schedule Trigger"다.

나는 15분마다 실행되도록 설정했다 — 워크플로우가 진정한 스케줄러처럼 동작하게 하기 위해서다. 10:00에 게시를 예약했다면, 수동으로 아무것도 실행하지 않고 다음 실행 사이클이 자동으로 처리해주길 원한다.

속도 제한과 동시성은 설계 자체로 암묵적으로 처리된다. 워크플로우는 실행당 최대 하나의 게시물만 발행한다. 15분 스케줄과 결합하면 API 사용량이 LinkedIn의 제한보다 훨씬 낮게 유지된다.

15분마다 실행하는 건 좋은 균형이다: 예약된 시간을 충분히 지킬 만큼 빠르고, 워크플로우 기록이 지저분해지지 않을 만큼 효율적이다. 더 정밀한 타이밍이 필요하면 1분마다 실행할 수도 있다.

부연: 시간대(timezone) 처리는 까다로울 수 있다. scheduled_at 값은 일관된 형식으로 저장하고, 비교는 Code 노드 내에서 이루어진다. 모호한 타임스탬프는 피하는 게 좋다.


Step 2: Google Sheets에서 행 읽기

다음은 LinkedInQueue에서 행을 불러오는 Google Sheets "Read" 노드다.

일부 n8n 설정에서는 "Sheets" 노드 자체에서 필터링을 할 수 있지만 (status가 queued인 것만), 나는 의도적으로 그 방법을 피했다. 외부 노드는 하나의 역할(데이터 가져오기)만 하고, 선택 로직은 한 곳에 명시적으로 모아두는 워크플로우를 선호하기 때문이다.

그 선택 로직은 다음 노드에 있다.


Step 3: 다음 예약 게시물 선택

n8n에서 모든 노드는 입력으로 아이템(JSON 객체)을 받고, 다음 노드로 아이템을 출력한다. 나의 "Code" 노드는 Google Sheets의 모든 행을 가져와서, status가 queued인 항목을 필터링하고, 가장 이른 예약 시간을 가진 항목을 선택한다.

정확한 코드는 다음과 같다:

 
javascript
const now = Date.now();

const queuedDue = $input.all()
  .filter(item =>
    item.json["status (queued, posting, posted, failed, skipped)"] === "queued"
  )
  .filter(item => {
    const t = new Date(item.json.scheduled_at).getTime();
    return !isNaN(t) && t <= now; // 예약 시간이 지난 항목만; 잘못된 날짜 제외
  })
  .sort((a, b) => {
    const da = new Date(a.json.scheduled_at).getTime();
    const db = new Date(b.json.scheduled_at).getTime();
    return da - db;
  });

return queuedDue.length ? [queuedDue[0]] : [{ json: { row_number: -1 } }];
```

n8n에 특화된 몇 가지 세부 사항:

- `$input.all()`은 이전 노드의 모든 행을 가져온다
- 각 행은 하나의 아이템이고, Google Sheets 행 데이터는 `item.json`에 저장된다
- 필터링으로 `status`가 `queued`인 행만 유지한다
- `scheduled_at` 기준 오름차순 정렬로 가장 이른 예약 행을 선택한다
- "Code" 노드는 단일 아이템을 배열로 감싸서 반환한다 — n8n이 기대하는 형식이다

> **핵심 포인트:** 이 코드는 `scheduled_at` 시간이 이미 지난 행만 반환한다. 미래로 예약된 행은 `status`가 `queued`여도 무시된다. 이 추가 조건이 스케줄러를 더 방어적으로 만든다 — 행이 너무 일찍 `queued`로 표시되더라도, 예약 시간이 지나야만 선택된다.

나는 코드를 단순하게 유지했다. 한눈에 이해할 수 있도록. 목표는 영리함이 아니라 **가독성**이다. 2주 뒤에 다시 봤을 때 다시 배워야 한다면, 그건 너무 복잡한 것이다.

---

### Step 4: 게시 전 행 잠금 (queued → posting)

게시하기 전에, 선택된 행의 상태를 `posting`으로 업데이트한다.

이게 불필요하게 느껴질 수 있다 — 실제로 이게 필요한 상황을 경험하기 전까지는. 잠금 단계 없이는 다음 상황에서 중복 게시가 발생할 수 있다:

- 일시적 오류 후 워크플로우가 재시도되는 경우
- 워크플로우가 짧은 간격으로 두 번 실행되는 경우
- LinkedIn이 느리게 응답하는 사이에 다시 트리거되는 경우
- 실행 도중 시스템이 재시작되는 경우

`posting` 상태는 단순한 잠금처럼 작동한다. 행이 `posting`이 되면 다시 선택 대상이 되지 않는다.

상태 전환 흐름:
```
queued → posting → posted
queued → posting → failed

재시도는 좋지만, 중복 게시는 나쁘다. 이런 잠금 단계가 공개적인 망신을 막아준다.


Step 5: LinkedIn 게시물 생성

행이 잠기면, 워크플로우는 n8n의 "LinkedIn" 노드를 사용해 내용을 게시한다. 텍스트만 올리기 때문에 페이로드는 단순하다: post_text 컬럼의 내용이 그대로 전달된다.

이 단계에서 워크플로우의 역할은 게시하는 것뿐이다 — 시트는 이미 이 항목이 처리 중임을 반영하도록 업데이트되어 있다.

참고: 이 워크플로우는 게시물이 LinkedIn의 텍스트 길이 제한 내에 있다고 가정하며, API 거부는 무조건 노출되어야 하는 치명적 오류로 처리한다. 대용량 사용 사례에는 추가적인 안전장치가 필요하다.


Step 6: IF 노드로 성공/실패 처리

"LinkedIn" 노드 이후, "IF" 노드를 사용해 게시가 성공했는지 실패했는지 확인한다.

이것이 가장 깔끔한 접근법이라는 게 증명됐다: 워크플로우 UI에서 읽기 쉽고, 두 결과를 모두 명시적으로 처리하기 때문이다.

성공 시 시트를 업데이트한다:

  • status = posted
  • posted_at = 현재 시각
  • 선택적으로 linkedin_ref 저장

실패 시 시트를 업데이트한다:

  • status = failed
  • error = <오류 메시지>

이렇게 하면 무슨 일이 있었는지 추측할 필요가 없다 — 시트가 전체 이야기를 알려준다.

첫 주 안에 실제로 오류가 하나 발생했다. LinkedIn API가 임시 인증 오류로 게시를 거부했다. n8n 실행이 실패했고, 해당 행은 시트에 오류 메시지와 함께 failed 상태로 남았다.

상태가 눈에 보였기 때문에 수정은 간단했다. LinkedIn 인증을 갱신하고, 해당 행의 상태를 다시 queued로 바꿨더니, 다음 예약 실행이 자동으로 처리해줬다.

이런 피드백 루프가 없었다면, 그 실패는 알아채지 못한 채 게시물이 그냥 사라졌을 것이다.

6. 직접 사용해보기

필요한 설정:

  1. 본인의 구글 시트 계정 연결.
  2. 링크드인 계정(Credentials) 연결.
  3. 시트의 열 이름과 노드 설정 일치 확인.

이 구조는 단순하기 때문에 스케줄 간격을 조정하거나, 승인 단계 또는 알림 노드를 추가하는 등 본인에 맞게 확장하기 쉽다.

7. 내가 중요하게 생각한 신뢰성 규칙

  1. 가시성(Visibility): 시트가 곧 대시보드다. 실패 원인도 거기서 바로 본다.
  2. 멱등성(Idempotency): 잠금 단계 덕분에 중복 작업이 발생하지 않는다.
  3. 단순함(Simplicity): 무언가 고장 났을 때 빨리 이해할 수 있어야 좋은 자동화다.

8. 향후 계획

다음 버전에서는 AI가 초안 작성을 돕는 기능을 넣고 싶다. 깃허브 활동이나 릴리스 노트를 바탕으로 AI가 초안을 만들고 시트에 draft 상태로 넣어두면, 내가 검토한 뒤 queued로 상태를 바꿔 게시하는 방식이다. 이렇게 하면 '내 목소리'를 유지하면서도 꾸준히 게시물을 올릴 수 있다.

결론: 이 워크플로우는 화려하진 않지만 반복적인 마찰을 제거해 준다. 꾸준함은 의지력이 아니라 더 나은 시스템에서 나온다.


부연 설명:

  • n8n: 복잡한 코딩 없이 마우스 클릭과 간단한 코드로 서비스 간 자동화를 만들어주는 툴이야. (Zapier나 Make의 오픈소스 버전 같은 느낌)
  • 멱등성(Idempotency): 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질을 말해. 여기서는 시스템 오류로 워크플로우가 다시 돌아가도 링크드인에 똑같은 글이 두 번 올라가지 않게 설계했다는 뜻이야.