Wedding RSVP Suite
wedding-rsvp-suite
by Wedding RSVP Suite · github.com/hermanno18/wedding-rsvp-suite · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/hermanno18/wedding-rsvp-suite/archive/refs/heads/master.zipA WordPress plugin that adds real guest management and RSVP tracking to any WordPress site — compatible with pretty much every page builder via shortcodes + Gutenberg blocks. No SaaS, no per-guest fee, your data stays in your own WordPress database.
Status: v2 (Gift Registry) built on top of the v1 RSVP foundation. Everything from v1 (foundation, RSVP core, admin UI, content blocks) plus a free Gift Registry: admin gift management (manual entry or "Preview from URL"), single-item + group gifts, a guest-facing registry widget reusing the same invite-code security model as RSVP, and a Thank You Tracker. Premium Stripe Cash Funds (v3) is the next roadmap item — see readme.txt for the full plan.
Folder structure
wedding-rsvp-suite/
├── wedding-rsvp-suite.php # Plugin bootstrap: header, constants, hooks, requires
├── includes/
│ ├── class-activator.php # Creates DB tables (RSVP + registry) + seeds settings + schedules cron
│ ├── class-i18n.php # Loads the wedding-rsvp-suite text domain
│ ├── class-license.php # Stub premium-license gate (always false pre-v3)
│ ├── class-rate-limiter.php # Shared per-IP sliding-window rate limiter (v1.1.0+ REST routes)
│ ├── class-rest-rsvp.php # REST routes: GET /lookup, POST /rsvp
│ ├── class-rest-registry.php # REST routes: POST /registry/scrape, GET /registry, POST /registry/reserve
│ ├── class-og-scraper.php # SSRF-hardened Open Graph URL scraper for "Add gift by URL"
│ ├── class-blocks.php # Registers all 8 Gutenberg blocks: dynamic RSVP + Registry + 5 static content blocks
│ └── class-cron-reminders.php # Daily wp-cron job emailing non-responders
├── admin/
│ ├── class-admin-menu.php # "Wedding Suite" admin menu (Guests/Events/Registry/Thank You Tracker/Settings)
│ ├── class-guest-list-table.php # WP_List_Table: household/guest/email/code/RSVP status
│ ├── class-guest-form.php # "Add Guest" form + shared invite-code generator
│ ├── class-guest-import-export.php # CSV export + CSV bulk import with per-row validation
│ ├── class-events-page.php # Event add/edit/delete admin UI
│ ├── class-settings-page.php # RSVP deadline + reminder email settings (Settings API)
│ ├── class-registry-page.php # Gift add/edit/delete form + "Preview from URL" flow
│ ├── class-registry-list-table.php # WP_List_Table: gifts, progress bars, contributor names
│ ├── class-thank-you-tracker.php # Filterable list matching contributors to gifts, toggle "sent"
│ ├── js/registry-admin.js # "Preview from URL" AJAX flow (properly enqueued, not inline)
│ └── css/registry-admin.css # Admin-only styles (image preview, progress bars, badges)
├── public/
│ ├── class-render.php # wrs_render_rsvp_form() + wrs_render_registry() — single source of truth markup+JS
│ ├── class-shortcodes.php # [wedding_rsvp] + [wedding_registry] shortcode wrappers
│ ├── js/registry.js # Guest-facing registry widget JS (browse/reserve/contribute)
│ └── css/registry.css # Guest-facing registry widget styles
├── blocks/
│ ├── rsvp/
│ │ ├── block.json # Dynamic block metadata (no save markup — PHP render_callback owns it)
│ │ ├── index.js # Minimal editor placeholder preview (no build step)
│ │ └── index.asset.php # Editor script dependency/version manifest
│ ├── registry/ # Dynamic block, same pattern as rsvp/ above (v2)
│ ├── countdown/ # Live-updating countdown timer (static block)
│ ├── story/ # "Our Story" vertical timeline (static block)
│ ├── party/ # Wedding party bios grid, MediaUpload photos (static block)
│ ├── schedule/ # Multi-day schedule grouped by day (static block)
│ └── faq/ # FAQ / travel info accordion (static block)
│ # Each static block folder: block.json, save.js, edit.js, style.css
│ # (+ view.js/view.asset.php for countdown & faq, which need frontend JS)
├── languages/
│ └── wedding-rsvp-suite.pot # Translation template (empty — no strings yet)
├── readme.txt # WordPress.org-format plugin readme
└── README.md # You are here
Custom DB tables created on activation (all prefixed wrs_):
| Table | Purpose |
|---|---|
wrs_households |
Groups guests together (e.g. "The Smith Family") |
wrs_guests |
One row per invited person, with a unique invite_code |
wrs_events |
Ceremony / reception / rehearsal, etc. |
wrs_rsvp_responses |
One row per guest per event |
wrs_registry_items (v2) |
One row per gift (manual or scraped-from-URL), simple or group |
wrs_gift_contributions (v2) |
One row per reservation/contribution against a registry item |
These are deliberately generic (plain BIGINT keys, no gift-fund assumptions baked in) so v3 (premium) can extend wrs_gift_contributions with Stripe payment/charge references later without a breaking migration.
Local testing
Pick whichever you already have installed:
Option A — LocalWP
- Create a new site in LocalWP (any PHP 8+/MySQL preset works).
- Symlink or copy this
wedding-rsvp-suite/folder into that site'swp-content/plugins/directory. - In
wp-admin→ Plugins, activate "Wedding RSVP Suite". Activation should complete with no PHP warnings/notices. - Check "Wedding Suite" appears in the left sidebar with a heart icon. On the Guests page, use "Add Guest" (or import a CSV) to create a household + guest; on the Events page, add at least one event.
- Configure a deadline and reminder sender on the Settings page (optional — only needed to test the reminder cron).
- Add
[wedding_rsvp]to a page, or insert the "RSVP Form" block, and confirm you can look the guest up by invite code or name and submit an RSVP. - Check the Guests list shows the updated RSVP status, and try the CSV export button to confirm the download works.
- Insert each of the 5 content blocks (Countdown, Our Story, Wedding Party, Schedule, FAQ) on a page from the block inserter and confirm they render on the published page — the countdown should tick live, and FAQ questions should expand/collapse on click.
- On the Registry admin page, add a gift manually, and separately try "Preview from URL" against a real public product page (the preview requires outbound internet access from your local WP install — it will legitimately fail against an offline/air-gapped environment, that's expected, not a bug). Add both a single-item gift and a group gift with a target amount.
- Add
[wedding_registry]to a page, or insert the "Gift Registry" block, and confirm the gifts you added appear. Reserve the single-item gift as one guest, then try again as a second guest and confirm you get a friendly "someone else just claimed this" message instead of a server error. Contribute a partial amount to the group gift and confirm the progress bar updates. - Check the Thank You Tracker shows your test contributions with real names (even ones you marked "keep my name private"), and confirm the Sent/Not Sent filter + toggle links work.
Option B — wp-env
- Install Node.js and
@wordpress/env(npm install -g @wordpress/env, requires Docker running). - From the plugin folder, run
wp-env start(add a.wp-env.jsonpointing"plugins": ["."]if this folder isn't already inside awp-env-managed project root). - Visit the printed local URL (usually
http://localhost:8888, wp-admin credentialsadmin/passwordby default), activate the plugin, and walk through the same checklist as Option A above.
Either way, activation should succeed with no PHP warnings/notices and should create the six wrs_* tables in the WordPress database (check via phpMyAdmin / Adminer / wp db query "SHOW TABLES LIKE '%wrs_%'") — all six are explicitly created as InnoDB, so the CSV importer's transaction wrapping (and the registry's atomic single-claim guard) rolls back/enforces reliably regardless of the server's default storage engine.
Known limitations in v1.1.0
- No guest Edit/Delete UI yet. The admin guest list table shows those row actions as "(coming soon)" — correcting a guest's details or removing one currently requires a direct database edit, or deleting + re-importing via CSV.
- No drag-and-drop reordering in the Story timeline or Schedule blocks — entries/days/items are reordered with simple "Move up"/"Move down" buttons in the block editor. Fine for the small counts a wedding website actually needs; a real drag-and-drop UI is a nice-to-have for a later release, not a v1 blocker.
- Rate limiting is per-IP, not per-guest. The RSVP REST API's brute-force protection (~10 requests / 5 minutes) is keyed on the visitor's IP address. Guests on the same shared/venue Wi-Fi or behind the same corporate NAT during a busy RSVP period (e.g. right after invitations go out) could theoretically hit that shared limit faster than expected. It resets on a sliding 5-minute window, so this should self-resolve quickly, but it's worth knowing about if guests report a "too many requests" error.
- No custom RSVP question builder yet — the
custom_answerscolumn exists in the schema for forward-compatibility, but there's no admin UI to define custom questions in v1; only meal choice and a free-text dietary-notes field are collected out of the box. - No "mark as purchased" flow for registry items. A reservation/contribution is always recorded with
status = 'reserved'— there's no admin or guest-facing action to flip it to'purchased'yet, even though the column exists for that purpose. Doesn't block real usage (the couple knows it's spoken for either way), just an incomplete status lifecycle for now. - OG scraping is best-effort. Sites with heavy client-side rendering (the actual product data only appears after JavaScript runs) won't have any Open Graph meta tags in the raw HTML this plugin fetches, so "Preview from URL" will come back empty and the admin has to fill in the fields manually — by design, this is flagged as a normal outcome, not a crash.
- No overfunding cap on group gifts. The guest-facing form doesn't prevent contributions that would push the total past the target amount; the progress bar just caps its visual fill at 100%. Not treated as a bug (a wedding registry going slightly over target isn't a problem), but worth knowing.
Coding conventions
- Everything prefixed
wrs_(functions) /WRS_(classes). if ( ! defined( 'ABSPATH' ) ) exit;guard at the top of every PHP file.- No Composer — plain
require_onceloading, this plugin is small enough not to need an autoloader yet. - All admin output escaped (
esc_html, etc.); all admin pages gated oncurrent_user_can( 'manage_options' ). - All
$wpdbqueries go through$wpdb->prepare();wrs_table_names()is the single place table names are derived from$wpdb->prefix. - The REST routes (
GET /lookup,POST /rsvp) are intentionally public (no WP auth) — see the docblock inincludes/class-rest-rsvp.phpfor the full threat-model reasoning. Both are IP rate-limited via transients (~10 req / 5 min),GET /lookuponly ever returns non-sensitive fields for household members other than the caller, andPOST /rsvprequires the caller to prove they know a valid invite_code for the household they're writing to (closes an IDOR where sequential household_id values could otherwise be brute-forced). wrs_render_rsvp_form()inpublic/class-render.phpis the ONLY place RSVP widget markup is written — the shortcode and the block both call it, so they can never drift out of sync. Its JS/CSS live inpublic/js/rsvp-form.jsandpublic/css/rsvp-form.css, registered + enqueued viawp_enqueue_script()/wp_enqueue_style()(never inline `