2026-08-21 · 대상: n8n 워크플로작가메일-수집의Code in JavaScript노드 하나
원본 백업:_백업_수집워크플로_Code노드_20260821.js(문제 생기면 그대로 되붙이면 원상복구)
Code 노드의 코드만 통째로 갈아 끼우면 끝납니다. 노드를 추가하거나 선을 다시 잇지 않아도 됩니다.
이유가 있습니다. n8n Code 노드에서 return null;을 쓰면 그 메일은 조용히 버려지고 워크플로는 정상 종료합니다. 실제로 격리 환경에서 돌려 확인했습니다.
| 시도 | 결과 |
|---|---|
return []; | ❌ 오류 (every key named 'json' must point to an object) |
return null; | ✅ 정상 종료 · 다음 노드로 아무것도 안 넘어감 |
즉 명단에 없으면 파일 자체가 안 만들어집니다. 광고가 수신함에 쌓이는 일이 사라집니다.
기존 If 노드(facebookmail·github·googleplay 3건 차단)는 그대로 두셔도 됩니다. 새 코드가 막는 범위 안에 들어가서 부딪히지 않습니다. 나중에 정리하고 싶으시면 그때 지우면 됩니다.
| 등급 | 판정 | 저장 | 현황판 |
|---|---|---|---|
| 🔴 되돌이 | 제목이 [작가활동]으로 시작 | ❌ 안 함 | - |
| 🟢 명단 | 발신자가 ALLOW에 있음 | ✅ 함 | ✅ 뜸 (needs_reply: true) |
| ⚪ 모르는 곳 | 그 외 전부 | ❌ 안 함 (기본값) | - |
🔴가 🟢보다 먼저입니다. 자기 알림 되돌이는 무조건 차단합니다.
🟢가 이깁니다. 명단에 있으면 발신 주소가 noreply여도 받습니다. 매체 시스템 메일이 noreply로 오는 경우가 있어서, 등록한 곳은 무슨 일이 있어도 놓치지 않게 했습니다.
명단에 없는 새 매체가 처음 연락하면 수신함에는 안 들어옵니다.
그래도 괜찮습니다. 지메일은 그대로 살아 있기 때문입니다. 이 도구는 메일함을 대체하는 게 아니라 마감을 추적하는 현황판입니다. 새 제안은 평소처럼 지메일에서 보시고, 계약이 되면 명단에 한 줄 추가하면 그때부터 자동 추적됩니다.
그래도 불안하시면 코드의 KEEP_UNKNOWN을 true로 바꾸세요. 모르는 곳 메일도 새얼굴 채널로 저장하되 현황판에는 안 띄웁니다.
수신함 62건의 발신 도메인을 전수 집계해서 만들었습니다. 추측이 아닙니다.
| 도메인 | 채널 | 실측 건수 |
|---|---|---|
wishket.com | 요즘IT | 19건 |
newneek.co | 퍼블리 | 2건 |
publy.co | 퍼블리 | 0건(예비) |
outstanding.kr | 아웃스탠딩 | 0건(예비) |
kmong.com | 크몽 | 1건 |
brunch.co.kr | 브런치 | 0건(예비) |
folin.co · ohmynews.com | 폴인 · 오마이뉴스 | 0건(예비) |
넣지 않은 것 두 가지
naver.com → 프리미엄콘텐츠 알림도 오지만 네이버 광고가 훨씬 많습니다. 9월 휴재 중이기도 해서 뺐습니다. 필요하면 주석을 푸세요.sit21c.com → 회사 도메인으로 보입니다(2건). 작가활동 현황판에 회사 메일이 섞이면 안 됩니다. 의도적으로 제외했습니다.http://localhost:5678 접속작가메일-수집 열기Code in JavaScript 노드 더블클릭Ctrl+A → Delete (기존 코드 전부 삭제)Save (또는 Ctrl+S)💡 4번에서 기존 코드가 사라지는 게 불안하시면, 붙여넣기 전에 백업 파일(_백업_수집워크플로_Code노드_20260821.js)이 이미 저장돼 있다는 것만 기억하세요.
// ═══════════════════════════════════════════════════════════════
// ① 여기만 고치면 됩니다
// ═══════════════════════════════════════════════════════════════
// 🟢 받을 곳 : 여기 적힌 곳에서 온 메일만 수신함에 저장됩니다.
// 새 매체와 계약하면 아래에 한 줄 추가하세요. (끝의 쉼표를 꼭 붙이세요)
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(/ /gi, " ").replace(/</gi, "<").replace(/>/gi, ">")
.replace(/&/gi, "&").replace(/"/gi, '"').replace(/'/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 이어야 합니다. 기존 설정 그대로이니 건드리지 마세요. (여기를 반대로 두면 오류 없이 조용히 틀린 결과가 나옵니다)
위 코드를 그대로 꺼내 Node로 문법 검사 + 9가지 상황 실행을 마쳤습니다. 프로덕션에 넣기 전에 확인한 결과입니다.
| 들어온 메일 | 판정 | 채널 |
|---|---|---|
요즘IT 편집자 (editor@wishket.com) | ✅ 저장 · 회신필요 | 요즘IT |
퍼블리 매니저 (@newneek.co) | ✅ 저장 · 회신필요 | 퍼블리 |
퍼블리 시스템 메일 (noreply@newneek.co) | ✅ 저장 · 회신필요 | 퍼블리 |
자기 알림 ([작가활동] 회신 마감 임박 12건) | 🚫 버림 | - |
| 알리익스프레스 광고 | 🚫 버림 | - |
| 깃허브 알림 | 🚫 버림 | - |
회사 도메인 (@sit21c.com) | 🚫 버림 | - |
처음 보는 매체 (editor@newmedia.co.kr) | 🚫 버림 | - |
| 크몽 | ✅ 저장 · 회신필요 | 크몽 |
세 번째 줄이 중요합니다. 발신 주소가 noreply여도 명단에 있으면 받습니다. 매체 시스템 메일을 놓치지 않기 위한 설계이고, 실제로 그렇게 동작합니다.
⚠️KEEP_UNKNOWN을true로 바꿔서도 돌려봤습니다. 그 경우 알리익스프레스 광고가새얼굴로 저장됩니다. 발신 주소에noreply같은 단어가 없는 광고는 걸러지지 않기 때문입니다.false권장입니다.
작가메일-수집 → Code in JavaScript 더블클릭ALLOW 목록에서 아무 줄 끝에 커서를 놓고 Enter { match: "새매체도메인.com", channel: "새매체이름" },
Save도메인은 어디서 확인하나요? 그 매체에서 온 메일을 지메일에서 열고, 보낸사람 주소의 @ 뒤를 그대로 쓰시면 됩니다. editor@abc.co.kr이면 abc.co.kr입니다.
채널 이름은 현황판 표의 "채널" 칸에 그대로 표시되고, 파일명에도 들어갑니다. 짧게 지으세요.
| 확인 | 방법 | 기대 결과 |
|---|---|---|
| ① 코드가 살아 있나 | 저장 후 오류 배지가 안 뜨는지 | 빨간 표시 없음 |
| ② 명단 메일이 들어오나 | 요즘IT·퍼블리 메일이 오면 수신함 확인 | md 파일 생성됨 |
| ③ 광고가 막히나 | 광고 메일이 와도 수신함에 안 생기는지 | 파일 없음 |
| ④ 되돌이가 막히나 | 내일 아침 알림 메일이 온 뒤 수신함 확인 | [작가활동] 파일 안 생김 |
④가 가장 중요합니다. 이게 확인되면 22건이 계속 늘어나던 문제가 끝난 겁니다.
이 수정은 앞으로 들어올 메일에만 적용됩니다. 이미 쌓인 39건의 미분류 파일은 그대로 남아 있습니다.
channel != "미분류" 필터가 걸려 있어서 안 보입니다_수신함 폴더를 열고 미분류가 들어간 파일을 직접 정리하시면 됩니다needs_reply만 false로 바꾸는 스크립트를 준비해 뒀습니다 (대상 23건 · 퍼블리·요즘IT 5건은 보존)