Skip to content

Instantly share code, notes, and snippets.

@chrishannah
Created April 2, 2026 16:06
Show Gist options
  • Select an option

  • Save chrishannah/0ae8889f255e3e2c8b2aeeb8370b3626 to your computer and use it in GitHub Desktop.

Select an option

Save chrishannah/0ae8889f255e3e2c8b2aeeb8370b3626 to your computer and use it in GitHub Desktop.
Implementation Plan: Miniship Email Notifications (Issue #25)

Implementation Plan: Email Notifications (Pro Feature)

Issue: chrishannah/miniship#25
Type: Pro Feature
Estimated Time: 8-12 hours

Overview

Implement email notification system for Miniship subscribers to receive updates when new releases are published. This is a Pro-tier feature requiring Stripe subscription validation.

Architecture

Database Schema

-- Subscribers table
CREATE TABLE subscribers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE,
  email TEXT NOT NULL,
  subscribed_at TIMESTAMPTZ DEFAULT NOW(),
  unsubscribed_at TIMESTAMPTZ,
  unsubscribe_token UUID DEFAULT gen_random_uuid(),
  notification_settings JSONB DEFAULT '{"release": true, "changelog": false}',
  UNIQUE(user_id, project_id)
);

-- Notification queue/log
CREATE TABLE notification_logs (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  subscriber_id UUID REFERENCES subscribers(id) ON DELETE CASCADE,
  release_id UUID REFERENCES releases(id) ON DELETE CASCADE,
  sent_at TIMESTAMPTZ DEFAULT NOW(),
  email_id TEXT, -- Resend email ID
  status TEXT DEFAULT 'sent', -- sent, failed, bounced
  error_message TEXT
);

-- Indexes
CREATE INDEX idx_subscribers_project ON subscribers(project_id) WHERE unsubscribed_at IS NULL;
CREATE INDEX idx_subscribers_user ON subscribers(user_id);
CREATE INDEX idx_notification_logs_release ON notification_logs(release_id);

Email Provider

Use Resend (already in stack per previous issues):

  • API key stored in environment variables
  • Rate limiting: 100 emails/hour on free tier
  • Need to verify sending domain

Implementation Steps

Phase 1: Database & Models (2-3h)

  1. Create migration (supabase/migrations/YYYYMMDDHHMMSS_email_notifications.sql)

    • Subscribers table
    • Notification logs table
    • RLS policies (subscribers can only see their own)
  2. Create TypeScript types (lib/types/notifications.ts)

    export interface Subscriber {
      id: string;
      userId: string;
      projectId: string;
      email: string;
      subscribedAt: string;
      unsubscribedAt?: string;
      unsubscribeToken: string;
      notificationSettings: {
        release: boolean;
        changelog: boolean;
      };
    }
  3. Database helpers (lib/db/subscribers.ts)

    • getSubscribers(projectId)
    • addSubscriber(userId, projectId)
    • removeSubscriber(userId, projectId)
    • unsubscribeByToken(token)

Phase 2: Email Templates (2-3h)

  1. Create email components (emails/)

    • Use React Email (react.email) for templates
    • NewReleaseEmail.tsx - main notification template
    • WelcomeEmail.tsx - subscription confirmation
  2. Email content structure

    Subject: [Project Name] New Release: v1.0.0
    
    - Release title & version
    - Changelog highlights (first 3 items)
    - Download link
    - View full release link
    - Unsubscribe link (footer)
    
  3. Resend integration (lib/email/resend.ts)

    import { Resend } from 'resend';
    
    const resend = new Resend(process.env.RESEND_API_KEY);
    
    export async function sendReleaseNotification(
      subscriber: Subscriber,
      release: Release
    ) {
      return await resend.emails.send({
        from: 'Miniship <releases@miniship.app>',
        to: subscriber.email,
        subject: `[${release.projectName}] New Release: ${release.version}`,
        react: NewReleaseEmail({ release, unsubscribeToken: subscriber.unsubscribeToken }),
      });
    }

Phase 3: Subscriber Management UI (2-3h)

  1. Public subscription form (app/[username]/[project]/subscribe/page.tsx)

    • Email input (pre-filled if authenticated)
    • Notification preferences checkboxes
    • Pro feature badge/gate
    • reCAPTCHA for anonymous subscribers
  2. Settings page (app/dashboard/projects/[id]/subscribers/page.tsx)

    • List active subscribers
    • Export subscriber list (CSV)
    • Subscriber count stats
    • Manual notification test button
  3. User subscription management (app/dashboard/subscriptions/page.tsx)

    • List projects user is subscribed to
    • Unsubscribe buttons
    • Notification preferences toggles

Phase 4: Release Hook Integration (1-2h)

  1. Trigger on release publish (app/api/releases/[id]/publish/route.ts)

    // After publishing release
    if (project.owner.isProUser) {
      await queueReleaseNotifications(release.id);
    }
  2. Background job (lib/jobs/send-release-notifications.ts)

    • Get all active subscribers for project
    • Batch send (max 50/minute to respect rate limits)
    • Log each send to notification_logs
    • Handle bounces/failures
  3. Optional: Use Inngest/Trigger.dev for job queue

    • More reliable than API route
    • Automatic retries
    • Better observability

Phase 5: Unsubscribe Flow (1h)

  1. Unsubscribe page (app/unsubscribe/page.tsx)

    • Token validation
    • Confirmation message
    • Optional: feedback form (why unsubscribing?)
  2. API route (app/api/unsubscribe/route.ts)

    • Validate token
    • Set unsubscribed_at
    • Return success/error

Phase 6: Pro Feature Gate (1h)

  1. Check subscription status

    // Only Pro users can enable notifications
    if (!user.hasProSubscription) {
      return <UpgradePrompt feature="Email Notifications" />;
    }
  2. Feature flag in project settings

    • Toggle "Email notifications enabled"
    • Disable during maintenance/rate limit issues

Environment Variables

Add to .env.example and production:

RESEND_API_KEY=re_xxxxx
RESEND_SENDING_DOMAIN=miniship.app
NOTIFICATION_RATE_LIMIT_PER_MINUTE=50

Testing Checklist

  • Subscribe to project notifications (authenticated)
  • Subscribe to project notifications (anonymous)
  • Publish new release → notification sent
  • Unsubscribe via email link
  • Notification preferences update
  • Pro user gates enforced
  • Email renders correctly (mobile + desktop)
  • Rate limiting works (mock 100+ subscribers)
  • Bounce handling
  • Re-subscribe after unsubscribe

Rollout Plan

  1. Deploy database migration
  2. Deploy email templates + API routes
  3. Test with beta users (Chris's projects)
  4. Monitor Resend dashboard for bounces/spam
  5. Enable for all Pro users
  6. Add to marketing site as Pro feature

Future Enhancements (Not in scope)

  • Digest mode (weekly summary instead of per-release)
  • Customize email frequency
  • Email preview before publish
  • Analytics (open rates, click rates)
  • Multi-language email templates

Dependencies

  • Resend account + API key
  • Domain verification for sending (miniship.app)
  • React Email library
  • Pro subscription validation logic

Risks & Mitigations

Risk: Email deliverability issues
Mitigation: Use Resend (good reputation), verify domain, include unsubscribe

Risk: Spam reports
Mitigation: Double opt-in, clear unsubscribe, only notify on actual releases

Risk: Rate limits on free Resend tier
Mitigation: Batch sends, upgrade plan if needed, queue system

Risk: Users forget they subscribed → spam complaints
Mitigation: Include context in email ("You subscribed to updates for Project X")

Estimated Timeline

  • Day 1: Database schema + models (Phase 1)
  • Day 2: Email templates + Resend integration (Phase 2)
  • Day 3: UI for subscription management (Phase 3)
  • Day 4: Release hook + background jobs (Phase 4)
  • Day 5: Unsubscribe flow + Pro gates (Phase 5-6)
  • Day 6: Testing + refinement

Total: 5-6 days (or 8-12 focused hours spread across time)


Ready to implement? Start with Phase 1 (database schema) to establish foundation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment