Documentation menu

캘린더에 추가 블록

원탭으로 "이것을 내 캘린더에 추가"하는 블록이며, 시간대가 서로 다른 초대장 문제에 대한 실용적인 해결책입니다. 발송된 이메일에는 수신자 자신의 시간대를 감지할 자바스크립트가 없으므로, 이 블록은 행사의 시작과 종료를 미리 계산된 현지 시간 문자열이 아니라 절대적인 시점으로 인코딩합니다. 그러면 각 참석자의 캘린더 앱이 자동으로 자신의 현지 시간에 맞게 올바르게 표시합니다.

작동 방식

이 블록은 장소 블록과 마찬가지로 순수한 콘텐츠입니다: 응답할 것이 없으므로 응답 대시보드나 CSV 내보내기에는 절대 표시되지 않습니다. 이 블록이 렌더링하는 세 개의 링크는 모두 전용 기능이 아니라, 일반 링크를 가진 모든 블록이 사용하는 것과 동일한 범용 클릭 추적 슬롯을 이용합니다.

세 링크는 유효한 startsAt 값이 있어야만 표시됩니다: 시작 시간이 입력되지 않은 행사는 제목과 설명 텍스트만 렌더링되며, 아무 데도 연결되지 않는 죽은 링크는 생기지 않습니다. startsAt은 설정되었지만 endsAt이 설정되지 않았거나(또는 startsAt보다 이전 시점으로 계산되는 경우), 블록은 시작 시각으로부터 1시간 후를 기본 지속 시간으로 사용합니다 — "시작 시각"만 입력하는 것이 일반적인 경우로 취급되며, 이후의 모든 소비자(.ics 파일, Google 캘린더, Outlook.com)는 어떤 형태로든 종료 시점이 존재해야 하기 때문입니다. location은 장소 블록이나 미팅 링크 블록의 텍스트를 참고해 직접 입력하는 일반 텍스트 필드입니다 — 이 첫 버전에서는 세 블록이 자동으로 연동되지 않으므로, 장소가 바뀌면 직접 갱신해야 합니다.

이 속성들로부터 세 개의 링크가 렌더링되며, 클릭 추적을 위해 하나의 blockId를 공유합니다(히트맵은 개별 링크가 아니라 블록 단위로 집계됩니다 — 소셜 링크 블록이 여러 아이콘에 사용하는 것과 같은 관례입니다):

  • .ics 다운로드 — 손수 작성한 RFC 5545(iCalendar) 파일을 스트리밍하는 GET /api/calendar로 연결되는 서명된 링크입니다. 이를 뒷받침하는 npm 의존성은 없습니다 — .ics 파일은 일반 텍스트이며, 카운트다운 GIF 인코더와 같은 "완전한 자체 제작" 방식으로 만들어집니다. 파일의 DTSTART/DTEND는 절대적인 UTC 시점으로 기록됩니다(뒤에 Z가 붙고, TZID/VTIMEZONE 구성 요소가 필요 없습니다) — 이것이 실제 시간대 문제의 해결책이며, 렌더링 시점에 계산되는 것이 아니라 블록의 startsAt/endsAt이 애초에 에포크 밀리초로 저장되어 있다는 사실의 자연스러운 결과입니다. 행사의 UID는 제목/설명/위치/시작/종료로부터 결정되는 sha256 해시이므로, 같은 서명된 링크를 다시 다운로드하면 새 항목이 생기는 대신 같은 캘린더 항목이 갱신됩니다. 자유롭게 입력한 텍스트는 RFC 5545 §3.3.11에 따라 이스케이프되며(백슬래시, 쉼표, 세미콜론, 실제 줄바꿈), 긴 줄은 규격이 요구하는 75옥텟 한도에서 접히므로, 관리자가 긴 설명을 입력해도 기술적으로 유효하지 않은 파일이 만들어지지 않습니다. URL 자체는 카운트다운/수식/차트 엔드포인트와 같은 방식으로 서명됩니다 — 행사 전체 페이로드가 쿼리 문자열로 직렬화되며(GET 경로에서는 Firestore 읽기가 없습니다), HMAC 입력에 내장된 "calendar:" 접두사를 통해 앱의 다른 모든 서명 URL 계열과 도메인이 분리되어 있으므로, 이 엔드포인트용으로 발급된 서명이 다른 엔드포인트에서 검증되는 일은 절대 없습니다.
  • Google 캘린더에 추가 — 발송 시점에 생성되는 단순한 calendar.google.com/calendar/render?action=TEMPLATE&... 딥링크로, 서버 왕복 통신이 없습니다.
  • Outlook.com에 추가 — 마찬가지로 발송 시점에 생성되는 단순한 outlook.live.com/calendar/0/action/compose?... 딥링크입니다.

제목, 설명, 위치는 세 링크가 만들어지기 전에 각각 고정된 최대 길이(200 / 2,000 / 300자)로 방어적으로 잘립니다 — 매우 긴 자유 입력 필드가 깨진 .ics 링크나 지나치게 큰 쿼리 문자열을 두 웹 링크에 만들어내지 않도록 하기 위함입니다.

설정 가능한 필드:

  • 행사 제목.
  • 설명(선택 사항).
  • 시작 시각 — 링크가 표시되려면 필수입니다.
  • 종료 시각(기본값은 시작 1시간 후).
  • 위치(선택 사항, 자유 텍스트).
  • 카드의 배경색, 패딩, 테두리, 모서리 반경.
  • 행사 제목의 색상/크기, 그리고 세 링크가 공유하는 색상/배경 재정의.

예시

Subject

Save the date: our fall meetup

모든 "행사" 목적 프로젝트는 미팅 링크, 장소, RSVP와 함께 캘린더에 추가 블록이 이미 배치된 상태로 시작합니다 — 초대부터 리마인더까지 이어지는 전체 패턴은 행사 & 웨비나를 참고하세요. 스포츠 클럽은 연습이나 경기 시간마다 같은 블록을 매주 재사용하므로, 어느 시간대를 이동 중이든 모든 가정의 캘린더가 정확한 일정을 반영합니다 — 스포츠 팀 & 클럽을 참고하세요. 웨비나 시리즈는 등록 직후 발송되는 확인 이메일에 이 블록을 넣어, 수신자가 나중에 기억해야 하는 부담 없이 등록한 바로 그 순간에 캘린더에 추가할 수 있게 할 수 있습니다.

정적 폴백의 모습

<div style="margin:12px 0;background-color:transparent">
  <p style="margin:0 0 8px;font-weight:600;font-size:16px;color:#111827">Q3 Product Launch</p>
  <a href="https://mailinapp.com/api/calendar?d=eyJ0aXRsZSI6...&s=abc123..."
     style="display:inline-block;padding:10px 20px;background-color:#4f46e5;color:#ffffff;
            border-radius:6px;text-decoration:none;font-size:14px;font-weight:600;margin:0 8px 8px 0">
    Download .ics
  </a>
  <a href="https://calendar.google.com/calendar/render?action=TEMPLATE&text=Q3+Product+Launch&dates=20260901T170000Z%2F20260901T180000Z"
     style="display:inline-block;padding:10px 20px;background-color:#4f46e5;color:#ffffff;
            border-radius:6px;text-decoration:none;font-size:14px;font-weight:600;margin:0 8px 8px 0">
    Add to Google Calendar
  </a>
</div>

이것이 메일에 발송되는 블록의 전부입니다 — 같은 행사 정보로 만들어진 세 개의 단순한 링크(여기서는 지면상 세 번째인 Outlook.com 링크는 생략했습니다)이며, 이미지나 가려진 콘텐츠는 전혀 관여하지 않습니다.

관련 페이지