2026-08-21 · Code 노드 1개만 교체 · 9가지 상황 실행 검증 완료

📮 수집 워크플로 v2 · 지정한 곳에서 온 메일만 받기

2026-08-21 · 대상: n8n 워크플로 작가메일-수집Code in JavaScript 노드 하나
원본 백업: _백업_수집워크플로_Code노드_20260821.js (문제 생기면 그대로 되붙이면 원상복구)

1. 결론 · 고칠 곳은 딱 한 군데입니다

Code 노드의 코드만 통째로 갈아 끼우면 끝납니다. 노드를 추가하거나 선을 다시 잇지 않아도 됩니다.

이유가 있습니다. n8n Code 노드에서 return null;을 쓰면 그 메일은 조용히 버려지고 워크플로는 정상 종료합니다. 실제로 격리 환경에서 돌려 확인했습니다.

시도결과
return [];❌ 오류 (every key named 'json' must point to an object)
return null;정상 종료 · 다음 노드로 아무것도 안 넘어감

명단에 없으면 파일 자체가 안 만들어집니다. 광고가 수신함에 쌓이는 일이 사라집니다.

기존 If 노드(facebookmail·github·googleplay 3건 차단)는 그대로 두셔도 됩니다. 새 코드가 막는 범위 안에 들어가서 부딪히지 않습니다. 나중에 정리하고 싶으시면 그때 지우면 됩니다.

2. 설계 · 3단 판정

등급판정저장현황판
🔴 되돌이제목이 [작가활동]으로 시작❌ 안 함-
🟢 명단발신자가 ALLOW에 있음✅ 함✅ 뜸 (needs_reply: true)
모르는 곳그 외 전부❌ 안 함 (기본값)-

🔴가 🟢보다 먼저입니다. 자기 알림 되돌이는 무조건 차단합니다.
🟢가 이깁니다. 명단에 있으면 발신 주소가 noreply여도 받습니다. 매체 시스템 메일이 noreply로 오는 경우가 있어서, 등록한 곳은 무슨 일이 있어도 놓치지 않게 했습니다.

⚠️ 화이트리스트의 유일한 약점과, 그게 괜찮은 이유

명단에 없는 새 매체가 처음 연락하면 수신함에는 안 들어옵니다.

그래도 괜찮습니다. 지메일은 그대로 살아 있기 때문입니다. 이 도구는 메일함을 대체하는 게 아니라 마감을 추적하는 현황판입니다. 새 제안은 평소처럼 지메일에서 보시고, 계약이 되면 명단에 한 줄 추가하면 그때부터 자동 추적됩니다.

그래도 불안하시면 코드의 KEEP_UNKNOWNtrue로 바꾸세요. 모르는 곳 메일도 새얼굴 채널로 저장하되 현황판에는 안 띄웁니다.


3. 명단 · 실제 데이터로 뽑았습니다

수신함 62건의 발신 도메인을 전수 집계해서 만들었습니다. 추측이 아닙니다.

도메인채널실측 건수
wishket.com요즘IT19건
newneek.co퍼블리2건
publy.co퍼블리0건(예비)
outstanding.kr아웃스탠딩0건(예비)
kmong.com크몽1건
brunch.co.kr브런치0건(예비)
folin.co · ohmynews.com폴인 · 오마이뉴스0건(예비)

넣지 않은 것 두 가지


4. 수정 절차 (약 5분)

  1. 브라우저에서 http://localhost:5678 접속
  2. 워크플로 목록에서 작가메일-수집 열기
  3. Code in JavaScript 노드 더블클릭
  4. 코드 영역 안을 클릭하고 Ctrl+ADelete (기존 코드 전부 삭제)
  5. 아래 §5 코드를 통째로 복사해서 붙여넣기
  6. 창을 닫고 우측 상단 Save (또는 Ctrl+S)
  7. 워크플로가 Active 상태인지 확인 (꺼져 있으면 켜기)
💡 4번에서 기존 코드가 사라지는 게 불안하시면, 붙여넣기 전에 백업 파일(_백업_수집워크플로_Code노드_20260821.js)이 이미 저장돼 있다는 것만 기억하세요.

5. ⌨️ 붙여넣을 코드 (전문)

// ═══════════════════════════════════════════════════════════════
//  ① 여기만 고치면 됩니다
// ═══════════════════════════════════════════════════════════════

// 🟢 받을 곳 : 여기 적힌 곳에서 온 메일만 수신함에 저장됩니다.
//    새 매체와 계약하면 아래에 한 줄 추가하세요. (끝의 쉼표를 꼭 붙이세요)
const ALLOW = [
  { match: "wishket.com",    channel: "요즘IT" },
  { match: "newneek.co",     channel: "퍼블리" },
  { match: "publy.co",       channel: "퍼블리" },
  { match: "outstanding.kr", channel: "아웃스탠딩" },
  { match: "kmong.com",      channel: "크몽" },
  { match: "brunch.co.kr",   channel: "브런치" },
  { match: "folin.co",       channel: "폴인" },
  { match: "ohmynews.com",   channel: "오마이뉴스" },
  // { match: "naver.com",   channel: "프리미엄콘텐츠" },   // 광고가 많아 기본 해제
  // { match: "새매체.com",  channel: "새매체이름" },        // ← 이렇게 추가
];

// 🔴 제목이 이걸로 시작하면 무조건 버립니다 (내가 나에게 보낸 알림 되돌이 차단)
const BLOCK_SUBJECT = ["[작가활동]"];

// 🟡 명단에 없는 곳에서 온 메일을 어떻게 할까요?
//    false = 아예 저장하지 않음 (권장)
//    true  = "새얼굴" 채널로 저장하되 현황판에는 안 띄움
const KEEP_UNKNOWN = false;

// 🟡 KEEP_UNKNOWN 이 true 일 때만 쓰입니다
const BLOCK_SENDER = ["noreply", "no-reply", "donotreply", "newsletter",
                      "notification", "mailer-daemon", "postmaster", "facebookmail"];

// ═══════════════════════════════════════════════════════════════
//  ② 아래는 건드리지 않으셔도 됩니다
// ═══════════════════════════════════════════════════════════════

const m = $input.item.json;

const fromRaw = (m.from?.value?.[0]?.address) || m.from || "";
const senderEmail = (String(fromRaw).match(/[\w.+-]+@[\w.-]+\.\w+/) || [String(fromRaw)])[0];
const f = (String(fromRaw) + " " + senderEmail).toLowerCase();
const subjectRaw = (m.subject || "제목없음").trim();

// ── 1) 자기 알림 되돌이 차단
if (BLOCK_SUBJECT.some(s => subjectRaw.startsWith(s))) return null;

// ── 2) 명단 대조
const hit = ALLOW.find(a => f.includes(a.match.toLowerCase()));

let channel, needsReply, status;
if (hit) {
  channel = hit.channel;
  needsReply = "true";
  status = "회신필요";
} else {
  if (!KEEP_UNKNOWN) return null;
  if (BLOCK_SENDER.some(s => f.includes(s))) return null;
  channel = "새얼굴";
  needsReply = "false";
  status = "확인대기";
}

// ── 3) 날짜
const today = new Date().toISOString().slice(0, 10);
const due = new Date(Date.now() + 2 * 86400000).toISOString().slice(0, 10);

// ── 4) 제목 정리
const safe = subjectRaw.replace(/[\\/:*?"<>|]/g, " ").replace(/\s+/g, " ").trim().slice(0, 50);

// ── 5) 본문 정제
function cleanBody(src) {
  let t = String(src || "");
  t = t.replace(/<(script|style)[\s\S]*?<\/\1>/gi, "");
  t = t.replace(/<br\s*\/?>/gi, "\n").replace(/<\/(p|div|tr|li|h\d)>/gi, "\n");
  t = t.replace(/<[^>]+>/g, "");
  t = t.replace(/&nbsp;/gi, " ").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">")
       .replace(/&amp;/gi, "&").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'");
  const cutMarks = [
    /^-+\s*Original Message\s*-+/im,
    /^-+\s*원본 메일\s*-+/im,
    /^\d{4}년 \d{1,2}월 \d{1,2}일.*작성/im,
    /^On .+wrote:/im,
    /상기 메일은 지정된 수신인/,
    /본 메일은 발신전용/,
    /무단으로 제3자에게 공개/,
    /The above message is intend/i,
    /This (e-?mail|message) (is|and any)/i,
  ];
  for (const re of cutMarks) {
    const idx = t.search(re);
    if (idx > 0) t = t.slice(0, idx);
  }
  t = t.replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
  return t;
}

let body = cleanBody(m.textPlain || m.textHtml || "");
if (!body) body = "(본문 없음)";
body = body.slice(0, 500);

const md = `---
type: mail
channel: ${channel}
direction: IN
subject: ${safe}
sender: ${senderEmail}
account: gmail
received: ${today}
due: ${due}
needs_reply: ${needsReply}
status: ${status}
---

# ${safe}

${body}

관련: [[_허브_마스터보드]]
`;

return { json: { fileName: `${today}_${channel}_${safe}.md`, md } };
Mode 확인: 이 노드의 실행 방식은 Run Once for Each Item 이어야 합니다. 기존 설정 그대로이니 건드리지 마세요. (여기를 반대로 두면 오류 없이 조용히 틀린 결과가 나옵니다)

5-1. ✅ 붙여넣기 전에 검증했습니다

위 코드를 그대로 꺼내 Node로 문법 검사 + 9가지 상황 실행을 마쳤습니다. 프로덕션에 넣기 전에 확인한 결과입니다.

들어온 메일판정채널
요즘IT 편집자 (editor@wishket.com)✅ 저장 · 회신필요요즘IT
퍼블리 매니저 (@newneek.co)✅ 저장 · 회신필요퍼블리
퍼블리 시스템 메일 (noreply@newneek.co)저장 · 회신필요퍼블리
자기 알림 ([작가활동] 회신 마감 임박 12건)🚫 버림-
알리익스프레스 광고🚫 버림-
깃허브 알림🚫 버림-
회사 도메인 (@sit21c.com)🚫 버림-
처음 보는 매체 (editor@newmedia.co.kr)🚫 버림-
크몽✅ 저장 · 회신필요크몽

세 번째 줄이 중요합니다. 발신 주소가 noreply여도 명단에 있으면 받습니다. 매체 시스템 메일을 놓치지 않기 위한 설계이고, 실제로 그렇게 동작합니다.

⚠️ KEEP_UNKNOWNtrue로 바꿔서도 돌려봤습니다. 그 경우 알리익스프레스 광고가 새얼굴로 저장됩니다. 발신 주소에 noreply 같은 단어가 없는 광고는 걸러지지 않기 때문입니다. false 권장입니다.

6. 🆕 새 매체와 계약했을 때 (30초)

  1. n8n → 작가메일-수집Code in JavaScript 더블클릭
  2. 맨 위 ALLOW 목록에서 아무 줄 끝에 커서를 놓고 Enter
  3. 한 줄 추가
  { match: "새매체도메인.com", channel: "새매체이름" },
  1. Save

도메인은 어디서 확인하나요? 그 매체에서 온 메일을 지메일에서 열고, 보낸사람 주소의 @ 뒤를 그대로 쓰시면 됩니다. editor@abc.co.kr이면 abc.co.kr입니다.

채널 이름은 현황판 표의 "채널" 칸에 그대로 표시되고, 파일명에도 들어갑니다. 짧게 지으세요.

7. 잘 적용됐는지 확인하는 법

확인방법기대 결과
① 코드가 살아 있나저장 후 오류 배지가 안 뜨는지빨간 표시 없음
② 명단 메일이 들어오나요즘IT·퍼블리 메일이 오면 수신함 확인md 파일 생성됨
③ 광고가 막히나광고 메일이 와도 수신함에 안 생기는지파일 없음
④ 되돌이가 막히나내일 아침 알림 메일이 온 뒤 수신함 확인[작가활동] 파일 안 생김

④가 가장 중요합니다. 이게 확인되면 22건이 계속 늘어나던 문제가 끝난 겁니다.


8. 과거분은 어떻게 하나요

이 수정은 앞으로 들어올 메일에만 적용됩니다. 이미 쌓인 39건의 미분류 파일은 그대로 남아 있습니다.