Version: 1.0
Last Updated: January 2026
Purpose: Comprehensive guide for testing all emails in the Alumni Lookup and Champion Portal applications
All emails in development are captured by Letter Opener and opened in your browser automatically.
View past emails:
tmp/letter_opener/
Clear all development emails:
rm -rf tmp/letter_opener/*
| Environment | Delivery Method | View Sent Emails |
|---|---|---|
| Development | Letter Opener (browser) | tmp/letter_opener/ |
| Staging | Postmark | Postmark Dashboard |
| Production | Postmark | Postmark Dashboard |
Cp::NotificationMailer)These emails are triggered by the notification system (Phase 8.3).
Purpose: Send critical notifications immediately (e.g., messages, mentions, new events)
Trigger:
# Happens automatically when notification is created with digest_preference: :immediate
# Or manually:
notification = Cp::Notification.last
Cp::NotificationMailer.immediate_notification(notification).deliver_now
Testing Steps:
digest_preference: "immediate"Purpose: Bundle multiple notifications into a single daily email (sent at 7am)
Trigger:
# Rails console - simulate daily digest
champion = Cp::Champion.find_by(email: "test@example.com")
notifications = champion.notifications.pending_email.limit(10)
Cp::NotificationMailer.daily_digest(champion, notifications, unread_threads: champion.unread_message_threads).deliver_now
Testing Steps:
digest_preference: "daily"Rake Tasks:
# Preview what would be sent (doesn't send email)
bin/rake "notifications:digests:preview_daily[email@example.com]"
# Send to a specific champion
bin/rake "notifications:digests:send_daily_to[email@example.com]"
# Send to all eligible champions
bin/rake notifications:digests:send_daily
Purpose: Weekly summary email with notifications, events, and stats (sent Monday 7am)
Trigger:
champion = Cp::Champion.find_by(email: "test@example.com")
notifications = champion.notifications.where("created_at > ?", 7.days.ago)
upcoming_events = Cp::Event.upcoming.limit(5)
week_stats = { new_champions: 5, messages_received: 3, posts_created: 2 }
Cp::NotificationMailer.weekly_digest(champion, notifications, upcoming_events: upcoming_events, week_stats: week_stats).deliver_now
Testing Steps:
digest_preference: "weekly"Rake Tasks:
# Preview what would be sent (doesn't send email)
bin/rake "notifications:digests:preview_weekly[email@example.com]"
# Send to a specific champion
bin/rake "notifications:digests:send_weekly_to[email@example.com]"
# Send to all eligible champions (bypasses Monday check)
bin/rake notifications:digests:send_weekly
Purpose: Notify Champion their verification is being processed
Trigger:
champion = Cp::Champion.find(id)
notification = champion.notifications.create!(
notifiable_type: "Cp::Champion",
notifiable_id: champion.id,
category: "account",
title: "Verification in Progress"
)
Cp::NotificationMailer.verification_pending_notification(notification).deliver_now
Testing Steps:
Purpose: Welcome email when Champion is verified
Trigger:
# Happens automatically when admin verifies a Champion
# Or manually:
champion = Cp::Champion.find(id)
notification = champion.notifications.where(category: "account").last
Cp::NotificationMailer.verification_approved_notification(notification).deliver_now
Testing Steps:
Purpose: Notify Champion when staff replies to their support thread
Trigger:
notification = Cp::Notification.where(category: "support").last
Cp::NotificationMailer.support_reply_notification(notification).deliver_now
Testing Steps:
Purpose: Notify Champion when they’re assigned as a Community Leader
Trigger:
notification = Cp::Notification.where(category: "community_leader").last
Cp::NotificationMailer.community_leader_assigned_notification(notification).deliver_now
Testing Steps:
Cp::CommunityMailer)Purpose: Notify staff when a Champion requests to join a community
Trigger:
join_request = Cp::JoinRequest.last
Cp::CommunityMailer.join_request_submitted(join_request).deliver_now
Testing Steps:
Purpose: Notify Champion their join request was approved
Trigger:
join_request = Cp::JoinRequest.last
Cp::CommunityMailer.join_request_approved(join_request).deliver_now
Testing Steps:
Purpose: Notify Champion their join request was denied
Trigger:
join_request = Cp::JoinRequest.where(status: "denied").last
Cp::CommunityMailer.join_request_denied(join_request).deliver_now
Testing Steps:
Purpose: Notify Champion when removed from a community
Trigger:
membership = Cp::CommunityMembership.last
Cp::CommunityMailer.member_removed(membership).deliver_now
Cp::ModerationMailer)Purpose: Notify Community Leader when content is auto-hidden due to flags
Trigger:
leader = Cp::Champion.community_leaders.first
content = Cp::BoardPost.hidden.last
community = content.board.community
Cp::ModerationMailer.content_auto_hidden(leader: leader, content: content, community: community, flag_count: 3).deliver_now
Testing Steps:
Purpose: Notify Engagement Team when CL escalates content
Trigger:
content = Cp::BoardPost.last
community = content.board.community
escalated_by = Cp::Champion.community_leaders.first
Cp::ModerationMailer.content_escalated(content: content, community: community, escalated_by: escalated_by, notes: "Needs review").deliver_now
Testing Steps:
Cp::SupportThreadMailer)Purpose: Notify support responders when CL creates support thread
Trigger:
support_thread = Cp::SupportThread.last
Cp::SupportThreadMailer.new_thread_notification(support_thread).deliver_now
Testing Steps:
can_support_respond: true receive emailPurpose: Notify CL when staff replies to their support thread
Trigger:
thread = Cp::SupportThread.last
message = thread.messages.last
Cp::SupportThreadMailer.staff_reply_notification(thread, message).deliver_now
Testing Steps:
Cp::AdminNotificationMailer)Purpose: Notify admins when new Champion signs up
Trigger:
champion = Cp::Champion.last
Cp::AdminNotificationMailer.new_champion_signup(champion).deliver_now
Testing Steps:
Purpose: Weekly summary of Champion Portal activity for staff
Trigger:
user = User.admin.first
Cp::AdminNotificationMailer.weekly_digest(user, period_start: 1.week.ago, period_end: Time.current).deliver_now
Testing Steps:
Cp::ChampionMailer)Purpose: Invite someone to become a Champion
Trigger:
inviter = Cp::Champion.verified.first
Cp::ChampionMailer.invite(inviter: inviter, recipient_email: "friend@example.com", personal_note: "Join us!").deliver_now
Testing Steps:
MessageMailer)Purpose: Notify Champion of new direct message
Trigger:
message = Cp::Message.last
recipient = message.thread.participants.where.not(champion_id: message.sender_id).first.champion
MessageMailer.new_message_notification(message: message, recipient: recipient).deliver_now
Testing Steps:
Cp::FeedbackMailer)Purpose: Send user feedback to the team
Trigger:
champion = Cp::Champion.first
Cp::FeedbackMailer.beta_feedback(champion: champion, feedback_type: "bug", message: "Test feedback", page_url: "/dashboard").deliver_now
Testing Steps:
ChampionSignupMailer)Purpose: Welcome new Champion after signup (legacy flow)
Trigger:
signup = ChampionSignup.last
ChampionSignupMailer.welcome_email(signup).deliver_now
Purpose: Notify admins of new signup (legacy flow)
Trigger:
signup = ChampionSignup.last
ChampionSignupMailer.admin_notification(signup).deliver_now
UserMailer)Purpose: Send one-time verification code
Trigger:
user = User.first
UserMailer.otp_email(user).deliver_now
Testing Steps:
Trigger: Click “Forgot Password” on login page
Trigger: New user signup (if confirmable enabled)
Trigger: Account locked after failed attempts
For each email type, verify:
| Check | Description |
|---|---|
| ☐ Subject Line | Clear, correct, includes relevant context |
| ☐ From Address | Correct domain (alumnichampions.com vs alumnilookup.com) |
| ☐ Reply-To | Set correctly (usually alumni@belmont.edu) |
| ☐ Recipient | Correct person receives the email |
| ☐ Content | All personalization tokens render correctly |
| ☐ Links | All links work and go to correct domain |
| ☐ Mobile | Email renders well on mobile |
| ☐ Footer | Includes unsubscribe/preferences link |
Run this in console to test all mailers compile:
# Test all mailers can be instantiated (doesn't send)
[
-> { Cp::NotificationMailer },
-> { Cp::CommunityMailer },
-> { Cp::ModerationMailer },
-> { Cp::SupportThreadMailer },
-> { Cp::AdminNotificationMailer },
-> { Cp::ChampionMailer },
-> { Cp::FeedbackMailer },
-> { MessageMailer },
-> { ChampionSignupMailer },
-> { UserMailer }
].each { |m| puts "#{m.call.name}: OK" }
Rails.application.config.action_mailer.delivery_method
# Should be :letter_opener in dev, :postmark in prod
ENV['POSTMARK_API_TOKEN'].present?
tail -f log/development.log | grep -i mail
Check default_url_options in the mailer. Champion Portal mailers should use:
{ host: "alumnichampions.com", protocol: "https" }
Required for production:
POSTMARK_API_TOKENMAILER_FROM_ADDRESSCHAMPION_PORTAL_HOSTLOOKUP_PORTAL_HOST# Find recent emails by type
Cp::Notification.where(category: "message").order(created_at: :desc).limit(5)
# Check Champion's notification preferences
champion = Cp::Champion.find_by(email: "...")
champion.notification_settings
# Preview an email in console
mail = Cp::NotificationMailer.immediate_notification(Cp::Notification.last)
puts mail.body.encoded
Note: In zsh, you must quote rake tasks with brackets to avoid glob expansion errors.
# List all champions with digest notifications configured
bin/rake notifications:digest_recipients
# Preview digests (doesn't send email)
bin/rake "notifications:digests:preview_daily[email@example.com]"
bin/rake "notifications:digests:preview_weekly[email@example.com]"
# Send digests to specific champion
bin/rake "notifications:digests:send_daily_to[email@example.com]"
bin/rake "notifications:digests:send_weekly_to[email@example.com]"
# Send digests to all eligible champions
bin/rake notifications:digests:send_daily
bin/rake notifications:digests:send_weekly
Document maintained by Engineering team. Update when new mailers are added.