Bloco Adicionar ao calendário
Um bloco de um toque para "adicionar isso ao meu calendário", e a solução prática para convites entre fusos horários diferentes. Um e-mail enviado não tem JavaScript para detectar o fuso horário do próprio destinatário, então este bloco codifica o início e o fim do evento como um instante absoluto, em vez de uma string de horário local pré-calculada. Dessa forma, o aplicativo de calendário de cada participante exibe o horário corretamente no seu próprio fuso horário local, automaticamente.
Como funciona
O bloco é conteúdo puro, como Local: não há nada a responder, então ele nunca aparece no painel de respostas nem na exportação de CSV. Os três links que ele renderiza usam o mesmo slot genérico de rastreamento de clique que todo bloco simples com link usa, em vez de uma capability sob medida.
Os links são bloqueados por ter um startsAt válido: um evento sem horário de início definido renderiza apenas o texto de título e descrição, sem links mortos apontando para lugar nenhum. Se startsAt está definido mas endsAt não (ou resolve para algo antes de startsAt), o bloco define a duração padrão como uma hora após o início — preencher apenas "quando começa" é tratado como o caso comum, e todo consumidor a jusante (o arquivo .ics, Google Calendar, Outlook.com) precisa que algum instante de término exista. location é um campo de texto livre digitado à mão, preenchido a partir do próprio texto do seu bloco Local ou Link de reunião; os três blocos não estão vinculados automaticamente nesta primeira versão, então atualize-o você mesmo se o local mudar.
A partir dessas propriedades, três links são renderizados, compartilhando um blockId para fins de rastreamento de clique (o mapa de calor agrupa por bloco, não por link individual, a mesma convenção que os links sociais usam para seus múltiplos ícones):
- Baixar .ics — um link assinado para
GET /api/calendarque transmite um arquivo RFC 5545 (iCalendar) feito à mão. Não há dependência npm por trás disso — um arquivo.icsé texto simples, construído da mesma forma "puramente do zero" que o codificador de GIF de contagem regressiva. Os camposDTSTART/DTENDdo arquivo são emitidos como instantes UTC absolutos (umZno final, sem componenteTZID/VTIMEZONEnecessário) — essa é a solução real para fusos horários diferentes, não algo calculado no momento da renderização, apenas uma consequência destartsAt/endsAtdo bloco já serem armazenados como milissegundos em época (epoch) desde o início. OUIDdo evento é um hash sha256 determinístico do título/descrição/local/início/fim, então baixar novamente o mesmo link assinado atualiza uma única entrada de calendário, em vez de criar uma duplicata. Texto livre digitado é escapado conforme a RFC 5545 §3.3.11 (barras invertidas, vírgulas, ponto e vírgula, quebras de linha literais) e linhas longas são quebradas no limite de 75 octetos exigido pela especificação, para que a descrição longa de um organizador não produza um arquivo tecnicamente inválido. A própria URL é assinada da mesma forma que os endpoints de contagem regressiva/fórmula/gráfico são — todo o payload do evento é serializado na string de consulta (sem leitura no Firestore no caminho GET), separado por domínio de toda outra família de URL assinada do app por meio de um prefixo"calendar:"embutido na entrada do HMAC, então uma assinatura gerada para este endpoint nunca pode ser verificada contra outro. - Adicionar ao Google Calendar — um deep link simples
calendar.google.com/calendar/render?action=TEMPLATE&...construído no momento da emissão, sem ida e volta ao servidor. - Adicionar ao Outlook.com — um deep link simples
outlook.live.com/calendar/0/action/compose?..., também construído no momento da emissão.
Título, descrição e local são cada um cortados de forma defensiva em tamanhos máximos fixos (200 / 2.000 / 300 caracteres) antes de qualquer um dos três links ser construído, para que um campo de texto livre muito longo não produza um link .ics quebrado ou uma string de consulta descomunal nos dois links da web.
Campos configuráveis:
- Título do evento.
- Descrição (opcional).
- Começa em — obrigatório para que qualquer link seja renderizado.
- Termina em (padrão de 1 hora após o início).
- Local (opcional, texto livre).
- Cor de fundo, preenchimento (padding), borda e raio da borda no cartão.
- Cor/tamanho do título do evento, e substituições de cor/fundo compartilhadas pelos três links.
Exemplos
Subject
Save the date: our fall meetup
Todo projeto de propósito "Evento" já começa com um bloco Adicionar ao calendário pronto ao lado de Link de reunião, Local e RSVP — veja Eventos e webinars para o padrão completo de convite até lembrete do qual este bloco faz parte. Um clube esportivo reutiliza o mesmo bloco semana após semana para horários de treino e partida, para que o calendário de cada família reflita a programação corretamente, independentemente do fuso horário em que estejam viajando — veja Times e clubes esportivos. Uma série de webinars pode incluir este bloco em um e-mail de confirmação enviado logo após a inscrição, para que o destinatário o adicione ao calendário no mesmo momento em que se inscreveu, em vez de depender de que ele se lembre depois.
Como é o fallback estático
<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>
Este é o bloco enviado por e-mail por completo — três links simples (o terceiro, Outlook.com, omitido aqui por brevidade) construídos a partir dos mesmos detalhes do evento, sem imagem ou conteúdo bloqueado envolvido.