Skip to content

Calendar invites for board meetings (both chapters) - #98

Merged
getvindicated2day merged 1 commit into
mainfrom
board-invites
Oct 12, 2026
Merged

getvindicated2day merged 1 commit into
mainfrom
board-invites

Conversation

@getvindicated2day

@getvindicated2day getvindicated2day commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

PR 2 of 2, on top of #97 (merged).

What changes

Schedule a meeting (admins only, Meetings tab, both chapters)

  • Fields: title, date, start and end time, location or Zoom link, notes, "Repeats weekly" with a last date, and who to invite: the whole board, one team, or picked members.
  • Before sending:
    • The form says how many people will get the invite and names anyone with no email on file.
    • Then it asks "Send invites to N people?" (or "Send updated invites…" for an edit).
  • "Send me a test invite": sends one invite to an address prefilled with the chapter's organizer (Fiona or Rana), which the admin can change. Nothing is saved.
  • Past meetings:
    • Upcoming meetings are listed with Edit and resend, Cancel this date (for weekly series), and Cancel the whole meeting.
    • Past dates of scheduled meetings with nothing logged show under "Waiting to be logged". That opens the existing log form, prefilled with the date and title.
  • My page: shows each person's upcoming meetings they're invited to, in date order with events.

The invites

  • Sending: through Brevo's SMTP relay with nodemailer, as agreed. Its icalEvent makes a text/calendar; method=REQUEST part (or CANCEL) plus an invite.ics attachment. Everything else still uses the Brevo API.
  • The .ics file:
    • METHOD:REQUEST or CANCEL.
    • One UID per meeting, <chapter>-<uuid>@getvindicated.org, stored with SEQUENCE in the new board_scheduled table.
    • A VTIMEZONE for America/Los_Angeles.
    • For weekly meetings, RRULE:FREQ=WEEKLY;BYDAY=…;UNTIL=…, with UNTIL in UTC. EXDATE lists dates that were cancelled.
    • Every invitee is an ATTENDEE with RSVP=TRUE.
    • The organizer is set per chapter in lib/board/chapter-server.ts.
  • Updates and cancellations:
    • An edit resends with the same UID and SEQUENCE+1. People removed get a cancellation, people added get the invite, and everyone else gets "Updated invitation".
    • Cancelling one date sends METHOD:CANCEL with RECURRENCE-ID. Cancelling the whole meeting sends METHOD:CANCEL and deletes it.
  • The emails follow docs/vindicated-invite-preview.html:
    • Subjects are "Invitation: …", "Updated invitation: …" or "Cancelled: …".
    • The body has a date card, a short note and the hub link.
    • They come from BOARD_INVITE_FROM, named "VINdicated at UCLA" or "VINdicated at Berkeley", with reply-to set to the organizer.

Railway variables to set

  • BREVO_SMTP_LOGIN: Brevo → SMTP & API → SMTP, the login shown there.
  • BREVO_SMTP_KEY: an SMTP key generated on that same page.
  • BOARD_INVITE_FROM: e.g. board@getvindicated.org.
    • The domain to verify in Brevo is getvindicated.org: Brevo → Senders, Domains & Dedicated IPs → Domains, then add the DNS records it shows.

Until all three are set, scheduling returns a clear "Invites aren't set up yet" message. The rest of the board works as before.

Tested locally

These were run against local Postgres, with BOARD_INVITE_OUTBOX writing each email to a file instead of sending it. The .eml files were checked with Python's email parser and the icalendar library.

  • Permissions:
    • A UCLA member trying to schedule gets 404.
    • A Berkeley admin can't cancel a UCLA meeting (404).
    • A bad cancel date returns 400.
  • Invite: the test invite went to one address and nothing was saved. Inviting the Data team sent 4 invites and named Ray as having no email on file.
  • MIME parts in every email: text/plain, text/html, text/calendar; method=REQUEST|CANCEL, and the invite.ics attachment.
  • The same UID throughout:
    • New invite: SEQUENCE 0, 6–6:30pm, RRULE:FREQ=WEEKLY;UNTIL=20261123T075959Z;BYDAY=SU, which is Nov 22 23:59:59 Pacific.
    • Edit to 6:30–7pm, with Allen removed and Rizwaan added: SEQUENCE 1. Updates went to 3 people, the invite to Rizwaan, and a cancellation to Allen.
    • Cancel Nov 22: SEQUENCE 2, METHOD:CANCEL, RECURRENCE-ID 2026-11-22 18:30 −08:00 (standard time handled).
    • Cancel the series: SEQUENCE 3, METHOD:CANCEL, and the row was deleted.
  • Screens:
    • The confirm step reads "Send invites to 9 people?".
    • The log form opens prefilled for a past date, and the date leaves "Waiting to be logged" once saved.
    • My page lists meetings and events in date order.
    • The Berkeley Meetings tab is unchanged apart from the new sections.
    • No sideways scrolling at 390px.
  • tsc, lint and build pass.

To check after deploy (can't be done from here)

  1. Set the three variables and verify getvindicated.org in Brevo.
  2. On /board/ucla → Meetings → Schedule a meeting, use Send me a test invite to your Gmail. It should show Yes / Maybe / No.
  3. Schedule a real one, edit its time, and check the event on your calendar moves instead of being duplicated.
  4. Cancel it and check the event disappears.

Generated by Claude Code

Admins schedule meetings on the Meetings tab: title, date, start and end,
location or Zoom link, notes, weekly with an end date, and who to invite
(whole board, one team, or picked members). The form shows who has no
email on file, asks "Send invites to N people?" and can send a test invite
to an address prefilled with the chapter's organizer.

Invites go through Brevo's SMTP relay with nodemailer so the .ics arrives
as text/calendar; method=REQUEST (or CANCEL) and Gmail, Apple Mail and
Outlook show Yes / Maybe / No. Each meeting keeps one UID; edits resend
with SEQUENCE+1 (removed invitees get a cancellation, added ones the
invite), and cancelling one date of a series sends CANCEL with
RECURRENCE-ID. The .ics has a VTIMEZONE for America/Los_Angeles, an RRULE
for weekly meetings and each invitee as an ATTENDEE with RSVP=TRUE.

Upcoming meetings show on the Meetings tab and on My page; past dates
with nothing logged offer the log form, prefilled.

New variables: BREVO_SMTP_LOGIN, BREVO_SMTP_KEY, BOARD_INVITE_FROM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QzNDQSmhjgKE63rwUnwcMq
@getvindicated2day
getvindicated2day merged commit 7cd1521 into main Oct 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants