Skip to content

feat(programs): non-member apply flow — emails the team (#140)#150

Merged
sacha-l merged 1 commit into
developfrom
feat/non-member-apply
May 22, 2026
Merged

feat(programs): non-member apply flow — emails the team (#140)#150
sacha-l merged 1 commit into
developfrom
feat/non-member-apply

Conversation

@sacha-l
Copy link
Copy Markdown
Collaborator

@sacha-l sacha-l commented May 22, 2026

Summary

Closes #140. Lets someone without a Stadium project apply to a program: a public form emails the team, who approve manually and reply — exactly the original ask.

Design decision (flag if you disagree)

The original feedback was "sends an email to info@joinwebzero.com with sacha@ cc'd with the person's request… then I add them in and they get a mail back saying they're approved." So the auto email goes only to the team (fixed recipients); the "you're approved" reply is sent manually later. I deliberately did not auto-send a confirmation to the applicant's address — doing so would let anyone use our verified Resend domain to email arbitrary addresses (open-relay / phishing amplification). The applicant's details live in the email body, never as a recipient.

Server

  • POST /api/programs/:slug/applications/non-memberpublic, no auth (program.routes.js).
  • program.controller.submitNonMemberApplication — honeypot check, resolve program, validate, send, return 200/400/404/503.
  • non-member-application.service.js — validates {name, email, walletAddress?, pitch}; sends one email to: info@joinwebzero.com, cc: [sacha@joinwebzero.com] with an HTML-escaped body. Returns 503 (graceful "email us directly") if Resend isn't configured.
  • email-transport.js — forward cc to Resend (backward-compatible).
  • Abuse mitigation: fixed recipients + honeypot field + validation. IP rate-limiting comes from the app-wide limiter in security: helmet headers + express-rate-limit (#127, #128) #149 once both merge.

Client

  • NonMemberApplyModal.tsx — name / email / wallet (optional) / pitch (≤1000) + hidden honeypot.
  • ProgramDetailPage — a "Don't have a Stadium project yet? Apply here ▸" CTA on open programs opens the modal.
  • api.submitNonMemberApplication(slug, payload) (public; mock-mode returns success).

Test plan

  • npm test (server) — 264 passed (7 new: validation + email to/cc/body + HTML-escaping).
  • npm run build + npm run lint (client) — green.
  • stadium-tester @ localhost (mock) — 6/6 PASS (CTA on open program; modal fields; empty-submit validation w/ no success; valid submit → success toast; completed program has no CTA).

Prod requirement

Needs RESEND_API_KEY + RESEND_FROM_EMAIL (verified domain) on the Railway env. Without them the endpoint returns 503 with a "please email info@joinwebzero.com directly" message — no crash.

Per CLAUDE.md §6: draft, never merging.

Lets someone without a Stadium project apply to a program. A public form
emails the team; they approve manually and reply (matching the original ask).

- POST /api/programs/:slug/applications/non-member (public, no auth): validates
  {name, email, walletAddress?, pitch}, honeypot field, then emails the team.
- non-member-application.service: sends ONE email to info@joinwebzero.com with
  sacha@joinwebzero.com cc'd and applicant details in the body. Fixed recipients
  only (applicant address never used as a recipient) so it can't be abused as an
  open relay. HTML-escaped body. Returns 503 if Resend isn't configured.
- email-transport: forward cc to Resend.
- Client: NonMemberApplyModal + a 'Don't have a Stadium project yet?' CTA on
  open program detail pages; api.submitNonMemberApplication.
- Tests: validation + the email to/cc/body + HTML-escaping.

Note: the original confirmation-to-applicant was intentionally dropped — it'd
let the endpoint email arbitrary addresses from our verified domain.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stadium Ready Ready Preview, Comment May 22, 2026 2:28am

@sacha-l sacha-l marked this pull request as ready for review May 22, 2026 02:33
@sacha-l sacha-l merged commit 7ef2a54 into develop May 22, 2026
2 checks 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.

feat: non-member apply flow with Resend email (info@ + sacha@ cc)

1 participant