구글 클라우드 회사 계정을 만들어서 이메일에서 필요한 데이터를 자동으로 추출해서 구글 시트에 기록하는 걸 만들고 있어요.


찾아보니 기본적으로 apps script가 있길래 한번 이거로 만들어봤더니 아주 만족스러웠습니다. 


근데 아쉽게도 기본 기능은 시간트리거만 가능해서 이메일이 수신되었을 때 실시간으로 작동은 안되더라구요 


구글 클라우드에 또 pub/sub 기능을 쓰면 이메일이 도착하면 작동하게 만들 수 있다고해서 적용을 해봤는데... 


이게 작동을 하는데 로그를 보면 doPost가 몇번 실행됬음에도 불구하고 새 이메일이 있음에도 그걸 파악하지 못하고 처리가 안되거나 1회성 트리거가 작동했는데 불구하고요


혹은 처리가 되어도 계속해서 doPost가 실행되는 오류가 생깁니다. 


수동으로  그냥 작동하면 잘 되는데... 


지금도 수동으로만 써도 정말 만족스럽긴 합니다만 기왕 시작한거 완전 자동으로 처리되게 하고 싶은데 정말 쉽지 않네요... 


도대체 뭘 확인을 해봐야할지 막막합니다.





### Gmail Pub/Sub 기반 Apps Script 프로그램 문제 요약



#### 1. **간략한 프로그램 구조**

- **목적**: Gmail에 새 이메일 도착 시 Pub/Sub 푸시 알림으로 받아서, 이메일 처리 로직(processUnreadInquiries())을 자동 실행. 처리 로직은 LLM(Gemini)으로 이메일 분류/추출 후 재고 조회, 답장 생성 등.

- **주요 컴포넌트**:

  - **doPost(e)**: Pub/Sub 푸시 엔드포인트. 푸시 받은 historyId를 확인하고, PENDING_HISTORY_ID (가장 큰 historyId)를 PropertiesService에 저장. 즉시 200 OK 반환 (재시도 방지).

  - **worker()**: 1회성 시간 기반 트리거로 호출됨. PENDING이 있으면 Gmail history를 증분 처리(_processGmailHistoryPush())해서 새 메시지만 골라 _processThread() 호출.

  - **watchdog()**: worker가 안 동작할 때 재스케줄링 (2분마다).

  - **processUnreadInquiries()**: 수동/폴링용. Gmail.search()로 UNREAD 이메일 찾아 처리 (이건 잘 동작함).

  - **setupGmailWatch()**: Gmail watch 설정 (INBOX만, 7일 만료).

- **환경**: Apps Script (Gmail/Drive/Spreadsheet API 사용), Pub/Sub 토픽/구독 설정됨.

- **저장소**: PropertiesService로 historyId 관리 (LAST_HISTORY_ID: 마지막 처리 baseline, PENDING_HISTORY_ID: 대기 중 max historyId).


#### 2. **자세한 실시간 작동 구조**

- **푸시 수신 시 (doPost)**:

  1. Pub/Sub가 푸시 → doPost가 JSON 파싱 (historyId 추출).

  2. historyId가 LAST보다 크면 PENDING 업데이트 (max 값으로 유지).

  3. worker 트리거 스케줄링 (_scheduleWorkerOnce(): 65초 후 1회성 트리거 생성).

  4. watchdog 스케줄링 (180초 후, worker가 안 돌면 재생성).

  5. 즉시 200 OK 반환 (ACK).

- **worker 실행 시**:

  1. Lock 획득 후 PENDING 확인.

  2. Gmail.Users.History.list()로 startHistoryId=LAST부터 messageAdded 이벤트만 뽑음.

  3. 새 메시지(INBOX/UNREAD 확인)만 _processThread()로 처리 (max 20개).

  4. 처리 후 LAST_HISTORY_ID = PENDING으로 업데이트, PENDING 삭제.

- **watchdog 역할**: worker 트리거가 유실되면 (e.g., 생성 후 안 터짐) 재생성.

- **전체 흐름**: 푸시 → 큐(PENDING) 쌓기 → 지연(1분 내) worker 처리 → 이메일 처리 후 아카이브/라벨링 (INBOX/UNREAD 제거로 후속 푸시 최소화).


#### 3. **주요 문제: 트리거 발화 불안정**

- **증상**: doPost가 푸시 받고 worker 트리거를 스케줄링(ScriptApp.newTrigger().timeBased().after(65000))하는데, 가끔 트리거가 안 터짐. (로그에 [SCHED] created worker... 나오지만, worker() 로그가 안 찍힘.)

- **가능 원인**:

  - Apps Script 시간 기반 트리거 유실 (Google 서버 측 이슈, 쿼터 초과, 지연).

  - Lock 경쟁: 여러 푸시가 동시에 와서 Lock.tryLock() 실패 → worker 스킵.

  - 트리거 중복/삭제 누락: _scheduleWorkerOnce()가 Lock으로 보호되지만, race condition 가능.

- **영향**: 새 이메일 도착해도 처리 안 됨. 수동 processUnreadInquiries() 호출 시엔 잘 됨.


#### 4. **주요 문제: 302 반복 호출**

- **증상**: doPost가 여러 번 반복 호출됨 (하나의 이메일 도착에 3~4회 푸시). 로그에 [PUSH] incoming historyId=... 반복.

- **가능 원인**:

  - Pub/Sub 재시도: doPost가 느리거나 오류 시 (e.g., PropertiesService 지연) 재시도. 하지만 코드상 항상 200 반환함.

  - Gmail history 이벤트 다중: messageAdded 외에 labelAdded (e.g., UNREAD) 이벤트로 추가 푸시.

  - 중복 푸시: Pub/Sub at-least-once 보장으로 동일 메시지 반복 전달.

  - 304 관련?: Pub/Sub 로그에서 304 (Not Modified) 에러가 보이는데, 이는 watch 설정 문제나 캐싱 이슈일 수 있음. (Gmail watch가 historyId를 제대로 업데이트 못 함?)

- **영향**: doPost 반복 → PENDING 중복 업데이트 → worker가 여러 번 불필요 실행 (하지만 Lock으로 막힘).


#### 추가 문제/관찰

- **푸시 지연**: Pub/Sub latencyMs=100~500ms, 하지만 worker 지연으로 전체 1~2분 걸림.

- **쿼터 초과 위험**: 매 푸시마다 PropertiesService 호출 → 하루 1000회 초과 시 에러.

- **테스트 결과**: 수동 watch/stop 후 이메일 보내면 doPost 1~몇 회 찍히지만, worker가 가끔 안 돔. 폴링(processUnreadInquiries())은 100% 성공.

- **시도한 해결**: Lock 타임아웃 늘림, 트리거 clean 함수 추가, BigInt로 historyId 관리 – 부분 개선 but 여전.


제발 살려주세요