린다 이메일 첨부파일 — 대용량 처리 아키텍처 분석 & 2026 최적안

슬랙 답장 기능 스레드 후속 · 2026-06-17 · 현재 코드 전수조사 + 메일 프로바이더 벤치마크 + S3 베스트프랙티스 + 의사결정 기준

0배경

슬랙 답장 기능 스레드(6/17)에서 첨부파일 관련 미해결 어젠다가 도출됨. 핵심 질문 두 가지.

  • 대용량 첨부 처리 — "용량이 크면 첨부가 안 된다". 현재 한계 ~22MB. 큰 파일을 어떻게 보낼 것인가.
  • 자동 다운로드 링크 — 김규동: "너무 클 때 자동으로 다운로드 링크를 제공하는 API는 없을까요?" / 조서현: browser→S3 직통 + 한 달 후 자동삭제 Lifecycle 필요, 범위가 커서 다음 어젠다로 제안.

이 문서는 그 결정을 내리기 위한 근거를 정리한다 — 우리 코드 현황, 업계 표준, 메일 서비스들의 실제 방식, S3 모범사례, 그리고 각 결정 포인트별 판단 기준.

1린다 현재 구현 (코드 전수조사)

결론: 모든 첨부가 서버(앱린다) 메모리를 경유하며, 용량 한계가 프론트·백엔드 불일치, 자동삭제(Lifecycle) 없음. presigned 직통 패턴은 녹음·CSV에만 존재.

업로드 경로 — 완전한 서버 경유

단계동작위치
1. 프론트파일 선택 → FormData(multipart)로 이메일 데이터와 함께 전송 (드래그앤드롭 미구현, 파일 input만)admin/src/components/FileAttachment.tsx
2. 서버 수신Elysia t.Files()메모리 버퍼 수신file.arrayBuffer() 전체 로드schemas/email.schemas.ts:57
3. 변환메모리에서 base64 변환 (이중 메모리 점유)utils/file.util.ts:7-11
4. S3 업로드PutObjectCommand로 서버→S3 전송services/email-send.service.ts:501-522

용량 제한 — 불일치 (SSOT 위반)

위치한계비고
FileAttachment 기본30MBFileAttachment.tsx:15
답장(Inline/New) 컴포저20MB프론트 검증만
시퀀스 단계30MB
백엔드 검증30MBemail-send.service.ts:494 하드코딩
⚠ 프론트 20MB / 백엔드 30MB 불일치 — 프론트 검증만 의존. 또한 22MB 실효 한계는 base64 인코딩(+33%) 때문에 30MB 설정이 실제로는 더 일찍 막히는 현상과 맞물림.

저장·다운로드

버킷 send-grid-test-assets리전 ap-northeast-2(서울) 이미지 → email-inline-images/ (public URL)그 외 → email-attachments/ (presigned GET 1h)

다운로드는 인증 라우트 GET /api/v1/emails/:id/attachments/:idx 경유. Lifecycle/TTL 없음 — 첨부가 영구 잔존(CacheControl: max-age=31536000, 1년 캐시). 단 CSV export에는 "24h presigned + lifecycle 정리 예정" 주석 존재.

이미 존재하는 직통 패턴 (재사용 가능)

  • 녹음 파일: presigned PUT 직통 업로드s3.service.ts:816-838 getPresignedRecordingUploadUrl()
  • CSV export: presigned GETs3.service.ts:688-705
✓ 즉, browser→S3 직통은 코드베이스에 이미 검증된 패턴이 있다. 첨부에 확장 적용하는 것이 핵심.

2이메일 첨부 기술 표준 — "왜 25MB인가"

한계는 임의 숫자가 아니라 base64 인코딩과 수신측 편차의 합산 결과. 이것이 임계값 결정의 1차 근거.

사실수치함의
base64 인코딩 증가+33% (실측 ~36~37%)20MB 원본 → ~27MB 전송. "25MB 한계"는 인코딩 후 기준
안전 원본 크기25MB cap 환경에서 ~18MB 이하24MB 원본은 인코딩 후 ~32MB로 반송됨
수신측 서버 한계 편차10MB ~ 50MB 제각각발신측이 작게 보내도 경로상 한 서버라도 초과하면 message too large 반송
deliverability 안전선전체 메시지 ~10MB 이하본문+인라인+인코딩 포함 전체에 적용
원본 크기업계 권장
< 1MB첨부
1–5MB1:1 발송이면 첨부
5–10MB신중히 첨부, 테스트 필수
> 10MB항상 다운로드 링크
근거: SMTP2GO(Goldilocks of email file sizes), MailSlurp email size limits, Mailchimp file size limit. Gmail은 25MB 초과 시 자동 Drive 링크 삽입.

3메일 프로바이더 벤치마크 (2026)

거의 모든 서비스가 ~25MB에서 클라우드 스토리지 다운로드 링크로 전환하는 동일 패턴. 차이는 만료 정책.

프로바이더인라인 한계초과 시대용량 상한보관·만료
Gmail (개인)25MB 발송자동 Google Drive 링크Drive 한도영속(사용자 삭제까지)
Gmail Workspace(Ent+)50MB 발송/70MB 수신자동 Drive 링크Drive 한도영속
Microsoft 365150MB(관리자)OneDrive 클라우드 링크파일당 250GB영속 / 링크 만료·비번 설정 가능
네이버 메일10MB자동 대용량 첨부파일당 2GB30일 / 100회 후 자동삭제
다음(카카오)25MB자동 대용량 첨부파일당 4GB30일 / 100회 후 자동삭제
Apple iCloud Mail Drop자동 업로드 링크메일당 5GB30일 후 만료
Proton Mail25MB 발송/50MB 수신Proton Drive 링크(수동)Drive 한도Drive 정책(E2E)
한국 사용자 대상 린다는 네이버/다음 모델이 직접 벤치마크 — 10~25MB 임계, 별도 스토리지, 30일 + 횟수 제한 자동삭제. 조서현이 제안한 "한 달 후 자동삭제"와 정확히 일치.
근거: Google/Microsoft/Apple/Proton 공식 support, kakao 고객센터(대용량 30일·100회·자동삭제), 네이버 메일 도움말.

4S3 활용 베스트프랙티스 (AWS 공식, 2026)

바이트는 클라이언트→S3 직행, 서버는 presign(작은 JSON)만. 서버 프록시는 대역폭 2배·OOM·게이트웨이 페이로드 한계(API GW 10MB)로 디폴트가 아님.

업로드 3가지 방식

방식최대용량 서버강제재개용도
Presigned PUT5GB불가불가소형, 강제 불필요
Presigned POST5GB가능 (content-length-range)불가용량 상한 필요한 사용자 업로드 ← 첨부
Multipart5TB파트별가능(병렬)5GB 초과·초대용량·재개
⚠ PUT은 크기를 서버가 강제할 수 없음 → 클라가 한계 우회 가능. 용량 제한이 필요하면 반드시 POST + content-length-range (policy에 암호 서명되어 우회 불가).
// 용량을 서버가 강제하는 유일한 방법 (POST policy)
createPresignedPost(s3, {
  Bucket, Key,
  Conditions: [["content-length-range", 0, 10*1024*1024]], // 0~10MB 강제
  Fields: { "Content-Type": "application/pdf" },
  Expires: 600,
});

다운로드 제공

방식CDN 캐시origin 은닉적합
Presigned GET일회성·저빈도·단일 파일 ← 첨부 다운로드
CloudFront + OAC signed URL반복 다운로드·캐시 이득
CloudFront signed cookie스트리밍·다수 파일

운영 필수 3종

  • Lifecycle: AbortIncompleteMultipartUpload 7일(미완료 파트 과금 누수 차단) + Expiration N일(임시 첨부 자동삭제)
  • CORS: ExposeHeadersETag 필수(누락 시 multipart 완료 실패), AllowedOrigins는 명시 origin
  • 보안: key는 서버 결정(테넌트 prefix 격리) · Content-Type 1차 + magic number 재검증 · GuardDuty Malware Protection for S3($0.09/GB)
✓ 린다 버킷은 서울 리전 + 한국 사용자S3 Transfer Acceleration 불필요(끄는 게 정답). 대륙간 전송이 아니므로 가속 효과 없이 비용만.
근거: AWS userguide(presigned URL / multipart / POST policy / CORS / lifecycle), GuardDuty Malware Protection for S3, AWS Compute Blog(Patterns for upload API).

5의사결정 기준 ★

각 결정 포인트 — 무엇을 정해야 하나 / 옵션 / 권장 / 근거. 린다(한국 사용자·서울 리전·B2B 이메일) 맥락 반영.

D1. 첨부 vs 다운로드 링크 전환 임계값을 몇 MB로?
옵션: 현행 ~22MB 유지 / 25MB / 원본 10MB / 15MB. → 권장: 원본 10MB 초과 시 자동 링크 전환. (한계값 자체는 단일 상수로)
근거: base64 +33%로 10MB→~13.5MB, 수신측 안전선 ~10MB, 업계 "10MB 초과는 항상 링크". 네이버도 10MB. 현행 20~30MB는 deliverability 반송 위험. 단, 보수적으로 시작하려면 15MB도 허용 범위.
D2. 업로드를 서버 경유 → 무엇으로 바꿀까?
옵션: 서버 경유 유지 / Presigned PUT / Presigned POST / Multipart. → 권장: Presigned POST (browser→S3 직통) + content-length-range로 용량 서버강제. 향후 100MB+ 허용 시 Multipart 추가.
근거: 용량 상한을 클라가 우회 못 하게 하려면 POST만 가능(PUT 불가). 첨부는 보통 수십 MB라 단일 POST로 충분. 녹음 업로드에 이미 presigned 직통 패턴 존재 → 재사용.
D3. 다운로드는 presigned GET vs CloudFront?
옵션: Presigned GET / CloudFront+OAC signed URL. → 권장: Presigned GET (만료 24h~7일).
근거: 첨부는 일회성·저빈도·단일 파일 → CDN 캐시 이득 없음. CloudFront는 인프라 과투자. 이미 CSV/첨부에 presigned GET 사용 중.
D4. 다운로드 링크(=S3 객체) 만료/보관 기간은?
옵션: 영속 / 7일 / 30일 / 90일. → 권장: S3 Lifecycle Expiration 30일. 메일 본문에 "이 링크는 30일간 유효" 명시.
근거: 네이버·다음·iCloud 모두 30일. 비용(영구 잔존 방지)과 UX(수신자 다운로드 여유) 균형. presigned GET 만료(접근권)와 S3 객체 만료(저장)는 별개 — 둘 다 설정.
D5. S3 Transfer Acceleration을 쓸까?
옵션: 켬 / . → 권장: 끔.
근거: 서울 리전 버킷 + 한국 사용자 = 같은 리전. 가속은 대륙간 장거리에서만 효과. 효과 없는데 $0.04~0.08/GB 추가만 발생.
D6. 멀웨어 스캔을 넣을까?
옵션: 없음 / GuardDuty Malware Protection for S3 / ClamAV+Lambda. → 권장: GuardDuty(여력 시), 1차 출시엔 보류 가능.
근거: 외부 수신자에게 파일을 호스팅·배포하므로 평판·법적 리스크 존재. GuardDuty는 관리형(운영부담 적음), $0.09/GB + 월 free tier. quarantine/ prefix 업로드 → 스캔 통과만 다운로드 허용 패턴. ClamAV는 운영부담 커 비권장.
D7. 용량 한계 상수의 SSOT는?
권장: 서버 모듈 상수 1곳 + 프론트가 API/공유 상수로 미러. 현재 20/30MB 불일치 즉시 제거.
근거: CLAUDE.md SSOT 원칙(서버 빌드 상수 = 모듈 export, FE는 BE 미러). content-length-range도 같은 상수로 강제.
D8. 본문 다운로드 링크의 deliverability(스팸 회피)는?
권장: 발신 도메인과 일치하는 링크 도메인 · full URL(단축 금지) · 명확한 안내문 · 링크 최소화 · SPF/DKIM/DMARC 정렬.
근거: 스팸 필터는 발신자와 다른 도메인·단축 URL·과다 링크를 의심. "첨부파일(○○.pdf, 12MB) 다운로드 — 30일간 유효" 식 명확 앵커.
D9. 자동 전환 UX — 사용자에게 어떻게 보일까?
옵션: 자동 무알림 / 자동 + 안내 배지 / 수동 선택. → 권장: 임계값 초과 시 자동 링크 전환 + "대용량이라 다운로드 링크로 첨부됩니다(30일 유효)" 인라인 안내.
근거: Gmail·네이버·다음 모두 자동 전환. 사용자 의사결정 부담 제거. 단 무엇이 일어나는지 투명하게 고지(신뢰).

62026 최적 제안 — 린다 하이브리드 첨부 파이프라인

소형은 그대로 인라인 첨부, 임계값 초과는 browser→S3 직통 + 자동 다운로드 링크 + 30일 자동삭제. 한국 메일 서비스 모델 + AWS 모범사례 결합.

흐름

[발송 시 파일 크기 분기]
  원본 ≤ 10MB  → 인라인 첨부 (기존 경로 유지, base64 MIME)
  원본 > 10MB  → 대용량 모드
      1) FE가 BE에 presigned POST 요청 (key=서버결정: ws/{wsId}/attachments/{uuid}/{file})
      2) BE: content-length-range[0, MAX] 조건으로 presigned POST 발급
      3) 브라우저 → S3 직통 업로드 (서버 메모리 미경유)
      4) (선택) GuardDuty 스캔 통과 확인
      5) 발송: 본문에 presigned GET 다운로드 링크 삽입
              "📎 ○○.pdf (24MB) — 다운로드 (30일간 유효)"
  [S3 Lifecycle] email-attachments/ → 30일 Expiration
                 + AbortIncompleteMultipartUpload 7일

업로드

Presigned POST 직통 · content-length-range 용량강제 · key 서버결정 · workspace prefix 격리

다운로드

Presigned GET (만료 24h, 만료 시 재발급) · 인증 라우트 경유 유지 가능

보관

S3 Lifecycle 30일 자동삭제 · 미완료 업로드 7일 정리 · 본문에 유효기간 명시

보안

magic number 재검증 · (선택)GuardDuty 스캔 · HTTPS · 테넌트 격리
✓ "작정하고 린다를 터뜨릴 수 있다"(조서현)는 우려 → 서버 메모리 미경유(OOM 차단) + content-length-range(용량 우회 차단) + Lifecycle(무한 적재 차단) + prefix 격리로 구조적 해소.

7구현 로드맵 (범위·우선순위)

1
즉시 (S, 버그·정합) — 프론트/백엔드 용량 한계 SSOT 통일(20/30 → 단일 상수), 임계값 정책 확정(D1). 코드 변경 작음.
2
핵심 (L, 직통 업로드) — presigned POST 발급 라우트 + FE 대용량 업로더(녹음 패턴 확장). content-length-range 강제. 서버 메모리 경유 제거.
3
운영 (M, 자동삭제·링크) — S3 Lifecycle 30일 + Abort 7일 설정, 발송 시 다운로드 링크 자동 삽입 + 유효기간 안내 카피.
4
강화 (M, 보안·옵션) — magic number 재검증, GuardDuty 스캔 도입 검토, 멀티파트(100MB+ 허용 시).
조서현 평가대로 2~3단계는 범위가 큰 작업(별도 어젠다). 단 1단계(SSOT·임계값)는 즉시 가능하고 deliverability 반송을 바로 줄임.

8부록 — 답장 UX 피드백 (코드 현황)

스레드의 나머지 피드백도 코드 기준 현황 정리. (별도 분석 영역)

피드백현황gap
답장 본문 수정 버튼있음 AiDraftReplyCard.tsx:437 "수정"/"저장" 토글구현됨
연필 아이콘없음 텍스트만아이콘 추가 가능
호버 커서 클릭가능 표시있음 본문 cursor-pointer단 하이라이트는 cursor-help(i) — 혼선 가능
"수정 내용 사라짐"버그 의심 다시생성/스레드전환 시 useEffect[draft.bodyText]localBody 덮어씀 (:117-122)미저장 편집값 보호 로직 필요
AI 초안 언어 선택UI 없음 BE는 language/locale 파라미터 지원, FE는 앱 UI 언어 고정 전송모달에 언어 드롭다운 + 선택값 전달
답장에 서명 삽입부분 시퀀스·AI답장은 자동 주입, 수동 답장(FloatingReplyPopup)은 없음답장 컴포저에 서명 선택/삽입
⚠ "수정 부분이 사라진다"는 단순 UX가 아니라 실제 상태관리 버그일 가능성이 높음 — 편집 중 다시 생성/스레드 전환 시 서버값이 로컬 편집값을 덮어쓰는 경로 확인 필요.