A small repo for hosting beautiful one-off invitations — baby showers, birthdays, dinner parties, anything.
Each event lives in its own folder with three files:
cards/
├── _template/ ← copy this folder to start a new card
│ ├── invite.html
│ ├── rsvp.html
│ ├── config.js
│ └── cover.png
├── atlas-xzmms2/ ← live event (slug = name + random suffix)
│ └── ... (same four files)
└── README.md ← you are here
URLs follow the pattern:
https://YOUR_USERNAME.github.io/cards/EVENT_SLUG/invite.html
Slugs use a random suffix so the URL you text isn’t easily guessable. The repo itself is public, but a stranger can’t predict the URL of a specific card without seeing the repo’s file list.
This walks you through: pushing the repo to GitHub, enabling Pages, setting up RSVPs for Atlas, and going live. ~15 minutes.
Timestamp | Event | Name | Email | Attending | Guests | Dietary | MessageCode.gs with the script from the RSVP setup section below, then save.https://script.google.com/macros/s/AKfycby.../execOpen atlas-xzmms2/config.js and find:
appsScriptUrl: "PASTE_YOUR_APPS_SCRIPT_WEB_APP_URL_HERE",
Replace the placeholder with your URL. Save.
# From inside the cards/ folder
git init
git add .
git commit -m "Initial cards setup"
git branch -M main
# Create the repo on GitHub (must be public for free Pages)
gh repo create cards --public --source=. --push
# Without gh CLI: create the repo at github.com/new (public, no README), then:
# git remote add origin https://github.com/YOUR_USERNAME/cards.git
# git push -u origin main
gh api repos/:owner/cards/pages \
-X POST \
-f "source[branch]=main" \
-f "source[path]=/"
Or in the browser: repo Settings → Pages → Source: Deploy from branch → main → / (root) → Save.
First activation takes ~1–2 minutes. After that, every push deploys in ~30 seconds.
Open https://YOUR_USERNAME.github.io/cards/atlas-xzmms2/invite.html on your phone. Tap envelope → submit RSVP → check your Sheet (should appear in 2 seconds) and your email at evan email (notification).
Paste the invite URL into Messages. iMessage auto-fetches the cover image as a link preview.
James and Sean are throwing a baby shower for me, Selina, and the soon-to-arrive Atlas. June 8th in DC — would love to have you there 🐳
[link]
Pro tip: text yourself first to confirm the link preview shows the cover image before sending to your guest list.
cd cards
# Generate an unguessable slug (6-char random suffix)
SUFFIX=$(LC_ALL=C tr -dc 'abcdefghijkmnpqrstuvwxyz23456789' </dev/urandom | head -c 6)
SLUG="my-event-${SUFFIX}" # e.g. my-event-x7k2m9
echo "Your slug: $SLUG"
cp -r _template "$SLUG"
cd "$SLUG"
# 1. Replace cover.png with your image
# 2. Edit config.js (every event-specific value lives there)
# 3. (Optional) Set up a Google Sheet + Apps Script for RSVPs;
# paste the Web App URL into config.js's appsScriptUrl field
cd ..
git add "$SLUG"
git commit -m "Add ${SLUG} card"
git push
Live in ~30 seconds at https://YOUR_USERNAME.github.io/cards/$SLUG/invite.html.
Pick the prefix freely — sarahs-30th, dinner-jun15, holiday-2026 — but always keep the random suffix so the URL is unguessable.
config.js — all event-specific values. Names, dates, location, copy, theme colors, registry/map links, RSVP destination. You only edit this file for a new card.
cover.png — the envelope cover image. Becomes the iMessage link preview when you text the URL. Aspect ratio ~1060×1500 (vertical).
invite.html — the invitation page. Reads from config.js. Don’t edit unless you want to change the design itself.
rsvp.html — the RSVP form. Also reads from config.js. Don’t edit.
Each card can have its own Google Sheet, or you can share one Sheet across cards (the RSVP page sends an event field that identifies which card it came from — handy for sorting).
Timestamp | Event | Name | Email | Attending | Guests | Dietary | Messagefunction doPost(e) {
try {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
const data = JSON.parse(e.postData.contents);
sheet.appendRow([
new Date(data.timestamp || new Date()),
data.event || '',
data.name || '',
data.email || '',
data.attending || '',
data.guests || '',
data.dietary || '',
data.message || ''
]);
// Optional: notify yourself
MailApp.sendEmail(
'evan@yedi.app',
`RSVP [${data.event}]: ${data.name} — ${data.attending}`,
[`Event: ${data.event}`,`Name: ${data.name}`,`Email: ${data.email}`,
`Attending: ${data.attending}`,`Guests: ${data.guests}`,
`Dietary: ${data.dietary}`,`Message: ${data.message}`].join('\n')
);
return ContentService.createTextOutput(JSON.stringify({ status: 'success' }))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService.createTextOutput(JSON.stringify({ status: 'error', message: err.toString() }))
.setMimeType(ContentService.MimeType.JSON);
}
}
function doGet(e) { return ContentService.createTextOutput('RSVP endpoint live.'); }
config.js as appsScriptUrl.Same setup, but reuse the same Web App URL across all your config.js files. The Event column tells you which card the RSVP came from.
config.js has a theme block with CSS color variables. Change them for a different look without touching the design code:
theme: {
ink: "#2a1e3f", // main text color
inkSoft: "#4a3c5c",
gold: "#d4a373", // accent
cream: "#f0e6d6",
creamWarm: "#faf3e0", // card background
bgGradient: "radial-gradient(ellipse at center, #2a1e3f 0%, #1a0e2f 100%)",
showStars: false, // turn off the starfield
}
The repo is public (required for free GitHub Pages), but each card’s folder uses an unguessable random suffix (e.g. atlas-xzmms2). The URL you text guests can’t be predicted without seeing the repo’s file listing on github.com.
A determined person could still browse to github.com/YOUR_USERNAME/cards and see every event you’ve ever made. For most events that’s fine. If you want stronger privacy, options:
Edit config.js (or replace cover.png), then:
git add .
git commit -m "Update [event] details"
git push
Live in ~30 seconds. iMessage caches link previews — append ?v=2 to the URL when re-sharing if the cover image changed.
git rm -r my-event-name
git commit -m "Archive my-event-name"
git push