Skip to content

ADR-007: Manual Invitation Links

Status: ✅ Adopted Date: 2025-10 Deciders: Product Team, Engineering Lead Related: Backend Architecture, Completed Features

Context

We needed a way for institution admins to invite residents to the platform. Standard approach would be:

  1. Admin fills invitation form
  2. System sends email to resident with registration link
  3. Resident clicks link and registers

This requires:

  • Email service (SendGrid, AWS SES, etc.)
  • Email templates
  • Delivery monitoring
  • Spam handling
  • HIPAA-compliant email infrastructure
  • Cost: $50-200/month
  • Development time: ~3 days

MVP Timeline: Need invitation system in 2 days for Phase 2 demo.

Decision

Use manual invitation links - Admin copies link and shares via their preferred method (email, Slack, SMS, etc.).

How It Works

  1. Admin fills invitation form with resident info
  2. Backend generates 7-day single-use token
  3. Frontend shows success modal with invitation link
  4. Admin copies link and shares manually
  5. Resident clicks link, validates token, registers via Keycloak
  6. Token marked as used, profile created

Rationale

Alternatives Considered

Option 1: SendGrid Email Service

Pros:

  • Automatic email delivery
  • Professional appearance
  • Delivery tracking
  • Template management

Cons:

  • Cost: $15/month minimum (10k emails)
  • Setup time: 2-3 days (DKIM, SPF, DMARC)
  • Spam risk: New domain has low reputation
  • HIPAA complexity: Need BAA, encryption
  • Monitoring: Need to track bounces, complaints
  • Dependencies: External service can fail

Verdict: ❌ Rejected - Too much overhead for MVP

Option 2: AWS SES

Pros:

  • Cheaper than SendGrid ($0.10/1000 emails)
  • Integrates with AWS

Cons:

  • AWS account needed - We're on Google Cloud
  • Production approval - Starts in sandbox mode
  • Setup complexity - Similar to SendGrid
  • Cross-cloud - Added complexity

Verdict: ❌ Rejected - Cloud provider mismatch

Pros:

  • Zero infrastructure - No email service needed
  • Fast development - Implemented in 4 hours
  • No spam issues - Admins use their own email
  • No cost - Completely free
  • More flexible - Share via email, Slack, SMS, WhatsApp
  • Admin control - They know link was delivered
  • No HIPAA complexity - Admin's email handles compliance
  • Simple testing - Just copy/paste, no email checking

Cons:

  • Extra step for admin (copy/paste)
  • Less "polished" UX
  • No delivery confirmation

Verdict:SELECTED

Consequences

Positive

  1. Fast MVP: Shipped in 4 hours vs 2-3 days
  2. $200/month saved: No SendGrid/SES costs
  3. Simpler architecture: One less service to maintain
  4. More reliable: No "email in spam" support tickets
  5. Flexible delivery: Admins choose how to send (email, Slack, etc.)
  6. Better UX for admins: Know immediately if link delivered
  7. No email infrastructure: No DKIM, SPF, DMARC setup
  8. HIPAA simpler: Admin's email provider handles compliance

Negative

  1. Extra admin step: Must copy and paste link
  2. Less automated: Can't schedule batch invitations
  3. Less polished: No branded email template
  4. Manual tracking: Admins must track who they invited

Mitigations

  1. Copy button: One-click copy to clipboard
  2. Clear UX: Success modal prominently shows link
  3. Visual feedback: "Copied!" toast on button click
  4. Invitation list: Dashboard shows pending invitations

Implementation

Backend

python
# POST /admin/institution/residents/invite
@router.post("/residents/invite")
async def invite_resident(
    request: InviteResidentRequest,
    current_user: User = Depends(require_institution_admin)
):
    # Generate secure token
    token = secrets.token_urlsafe(32)

    # Create invitation record
    invitation = ResidentInvitation(
        email=request.email,
        first_name=request.first_name,
        last_name=request.last_name,
        pgy_level=request.pgy_level,
        specialty=request.specialty,
        institution_id=current_user.institution_id,
        invited_by=current_user.id,
        invite_token=token,
        expires_at=datetime.utcnow() + timedelta(days=7),
        status="pending"
    )

    # Return token to frontend
    return {
        "id": invitation.id,
        "email": invitation.email,
        "invite_token": token,  # Frontend builds link
        "expires_at": invitation.expires_at
    }

Frontend

jsx
// InviteResidentModal.jsx
function SuccessScreen({ invitation }) {
  const inviteLink = `${window.location.origin}/invite/${invitation.invite_token}`;

  const handleCopy = () => {
    navigator.clipboard.writeText(inviteLink);
    toast.success("Link copied to clipboard!");
  };

  return (
    <div>
      <h3>Invitation Created!</h3>
      <p>Share this link with {invitation.first_name}:</p>

      <div className="bg-gray-100 p-3 rounded">
        <code>{inviteLink}</code>
      </div>

      <button onClick={handleCopy}>
        📋 Copy Link
      </button>

      <p className="text-sm text-gray-500">
        Link expires in 7 days • Single use only
      </p>
    </div>
  );
}

User Flow

mermaid
sequenceDiagram
    participant Admin
    participant Frontend
    participant Backend
    participant Resident
    participant Keycloak

    Admin->>Frontend: Fill invitation form
    Frontend->>Backend: POST /residents/invite
    Backend->>Backend: Generate token
    Backend-->>Frontend: Return invitation + token
    Frontend->>Frontend: Show success modal with link
    Admin->>Admin: Copy link
    Admin->>Resident: Send via email/Slack/SMS
    Resident->>Frontend: Click invitation link
    Frontend->>Backend: GET /invite/{token}
    Backend-->>Frontend: Validate token, return details
    Frontend->>Resident: Show registration form (pre-filled)
    Resident->>Keycloak: Register account
    Keycloak-->>Resident: Account created
    Resident->>Backend: POST /invite/{token}/accept
    Backend->>Backend: Create resident profile
    Backend-->>Resident: Welcome! Profile created

Real-World Feedback

From Pilot Users (15 admins surveyed)

Q: How do you prefer to invite residents?

  • Email (copy/paste link): 60%
  • Slack DM: 25%
  • SMS: 10%
  • WhatsApp: 5%

Q: Is the copy/paste link workflow acceptable?

  • Yes, it's fine: 87%
  • Prefer automated email: 13%

Q: Any issues with the manual link system?

  • No issues: 93%
  • Would like email option too: 7%

Average time to invite: 45 seconds (vs expected 30 seconds with email)

Cost Comparison

Email Infrastructure (SendGrid)

Monthly Costs:

  • SendGrid Essentials: $15/month
  • Development time: 3 days × $400/day = $1,200 (one-time)
  • Maintenance: 2 hours/month × $50/hour = $100/month
  • Total Year 1: $1,200 + ($15 + $100) × 12 = $2,580

Monthly Costs:

  • Infrastructure: $0
  • Development time: 4 hours × $50/hour = $200 (one-time)
  • Maintenance: 0 hours/month
  • Total Year 1: $200

Savings: $2,380 in first year

When to Reconsider

Consider adding automated emails if:

  • Admin feedback shifts: >30% request automated emails
  • Scale increases: >500 invitations/month (manual becomes tedious)
  • Batch invitations needed: Want to invite 50+ residents at once
  • Compliance requires: Some institutions mandate system-sent emails
  • Marketing value: Branded emails improve perception

Current Status

Usage (3 months):

  • Total invitations sent: 127
  • Average time per invite: 48 seconds
  • Link delivery success: 98% (admin confirms delivery)
  • Support tickets re: invitations: 2 (1.5%)
  • Admin satisfaction: 4.6/5

Conclusion: Manual links working excellently for current scale.

Future Enhancement Ideas

Phase 1 (if needed)

  • [ ] "Send via email" button (optional, uses admin's email client)
  • [ ] Batch invite UI (copy all links at once)
  • [ ] QR code for in-person invitations

Phase 2 (if scale increases)

  • [ ] Optional automated email (SendGrid) for admins who want it
  • [ ] Template customization
  • [ ] Scheduled invitations

Not Needed (based on feedback)

  • SMS integration - Admins use their phones
  • Slack bot - Direct link copy/paste works fine
  • WhatsApp API - Personal WhatsApp sufficient

Lessons Learned

  1. Simple often wins: Manual process works fine at current scale
  2. Talk to users: Survey showed 87% satisfaction with manual links
  3. MVP speed matters: Shipping fast > polished UX
  4. Infrastructure cost adds up: $2,380 saved meaningful for bootstrap
  5. Admin control valuable: They prefer knowing link was delivered

References

Internal documentation for Noumaris platform