$npx -y skills add ThibautBaissac/rails_ai_agents --skill mailer-patternsCreates minimal Action Mailer classes with bundled notification patterns following 37signals conventions. Use when sending emails, creating notification systems, digest emails, or when user mentions mailers, emails, notifications, or transactional messages. WHEN NOT: For backgrou
| 1 | # Mailer Patterns |
| 2 | |
| 3 | ## Philosophy: Minimal Mailers, Bundled Notifications |
| 4 | |
| 5 | - Plain-text first, minimal HTML styling with inline CSS |
| 6 | - Bundle notifications instead of sending one email per event |
| 7 | - Transactional emails only (no marketing campaigns) |
| 8 | - `deliver_later` for individual emails; `deliver_now` is acceptable inside background jobs that already run asynchronously (e.g., digest delivery jobs) |
| 9 | - Email previews for development |
| 10 | - No email service abstraction layers (use Action Mailer directly) |
| 11 | |
| 12 | ## Project Knowledge |
| 13 | |
| 14 | **Stack:** Action Mailer (built-in), Solid Queue for background delivery, |
| 15 | email previews in development, plain text + HTML multipart emails. |
| 16 | |
| 17 | **Multi-tenancy:** Account-scoped emails, from address includes account |
| 18 | context, unsubscribe links scoped to account. |
| 19 | |
| 20 | **Commands:** |
| 21 | ```bash |
| 22 | rails generate mailer Comment mentioned # Generate mailer |
| 23 | rails generate mailer Digest daily_activity # With methods |
| 24 | # Visit http://localhost:3000/rails/mailers # Preview emails |
| 25 | ``` |
| 26 | |
| 27 | ## Pattern 1: Simple Transactional Mailers |
| 28 | |
| 29 | ```ruby |
| 30 | # app/mailers/application_mailer.rb |
| 31 | class ApplicationMailer < ActionMailer::Base |
| 32 | default from: ENV.fetch("MAILER_FROM_ADDRESS", "Fizzy <support@fizzy.do>") |
| 33 | layout "mailer" |
| 34 | end |
| 35 | |
| 36 | # app/mailers/comment_mailer.rb |
| 37 | class CommentMailer < ApplicationMailer |
| 38 | def mentioned(mention) |
| 39 | @mention = mention |
| 40 | @comment = mention.comment |
| 41 | @card = mention.comment.card |
| 42 | @account = mention.account |
| 43 | |
| 44 | mail( |
| 45 | to: mention.user.email, |
| 46 | subject: "#{mention.creator.name} mentioned you in #{@card.title}" |
| 47 | ) |
| 48 | end |
| 49 | |
| 50 | def new_comment(comment, recipient) |
| 51 | @comment = comment |
| 52 | @card = comment.card |
| 53 | @account = comment.account |
| 54 | |
| 55 | mail( |
| 56 | to: recipient.email, |
| 57 | subject: "New comment on #{@card.title}" |
| 58 | ) |
| 59 | end |
| 60 | end |
| 61 | |
| 62 | # app/mailers/membership_mailer.rb |
| 63 | class MembershipMailer < ApplicationMailer |
| 64 | def invitation(membership) |
| 65 | @membership = membership |
| 66 | @account = membership.account |
| 67 | @inviter = membership.inviter |
| 68 | |
| 69 | mail( |
| 70 | to: membership.user.email, |
| 71 | subject: "#{@inviter.name} invited you to #{@account.name}" |
| 72 | ) |
| 73 | end |
| 74 | end |
| 75 | |
| 76 | # app/mailers/card_mailer.rb |
| 77 | class CardMailer < ApplicationMailer |
| 78 | def assigned(assignment) |
| 79 | @assignment = assignment |
| 80 | @card = assignment.card |
| 81 | @account = assignment.account |
| 82 | |
| 83 | mail( |
| 84 | to: assignment.user.email, |
| 85 | subject: "#{assignment.assigner.name} assigned you to #{@card.title}" |
| 86 | ) |
| 87 | end |
| 88 | end |
| 89 | ``` |
| 90 | |
| 91 | ## Pattern 2: Email Templates |
| 92 | |
| 93 | See @references/mailer-templates.md for full template examples. |
| 94 | |
| 95 | Always create both `.text.erb` and `.html.erb` versions: |
| 96 | |
| 97 | ```erb |
| 98 | <%# app/views/comment_mailer/mentioned.text.erb %> |
| 99 | Hi <%= @mention.user.name %>, |
| 100 | |
| 101 | <%= @mention.creator.name %> mentioned you in a comment on <%= @card.title %>: |
| 102 | |
| 103 | "<%= @comment.body %>" |
| 104 | |
| 105 | View the card: <%= account_board_card_url(@account, @card.board, @card) %> |
| 106 | |
| 107 | --- |
| 108 | You're receiving this because you were mentioned. |
| 109 | |
| 110 | <%# app/views/comment_mailer/mentioned.html.erb %> |
| 111 | <p>Hi <%= @mention.user.name %>,</p> |
| 112 | |
| 113 | <p><%= @mention.creator.name %> mentioned you in a comment on |
| 114 | <strong><%= @card.title %></strong>:</p> |
| 115 | |
| 116 | <blockquote style="border-left: 3px solid #ccc; padding-left: 15px; color: #666;"> |
| 117 | <%= simple_format(@comment.body) %> |
| 118 | </blockquote> |
| 119 | |
| 120 | <p><%= link_to "View the card", |
| 121 | account_board_card_url(@account, @card.board, @card), |
| 122 | style: "color: #0066cc; text-decoration: none;" %></p> |
| 123 | ``` |
| 124 | |
| 125 | ## Pattern 3: Bundled Notifications (Digest Emails) |
| 126 | |
| 127 | See @references/bundled-notifications.md for full details. |
| 128 | |
| 129 | ```ruby |
| 130 | # app/mailers/digest_mailer.rb |
| 131 | class DigestMailer < ApplicationMailer |
| 132 | def daily_activity(user, account, activities) |
| 133 | @user = user |
| 134 | @account = account |
| 135 | @activities = activities |
| 136 | @grouped_activities = activities.group_by(&:subject_type) |
| 137 | |
| 138 | mail( |
| 139 | to: user.email, |
| 140 | subject: "Daily activity summary for #{account.name}" |
| 141 | ) |
| 142 | end |
| 143 | |
| 144 | def pending_notifications(user, notifications) |
| 145 | @user = user |
| 146 | @notifications = notifications |
| 147 | @accounts = notifications.map(&:account).uniq |
| 148 | |
| 149 | mail( |
| 150 | to: user.email, |
| 151 | subject: "You have #{notifications.size} pending notifications" |
| 152 | ) |
| 153 | end |
| 154 | end |
| 155 | |
| 156 | # app/models/notification_bundler.rb |
| 157 | class NotificationBundler |
| 158 | def initialize(user) |
| 159 | @user = user |
| 160 | end |
| 161 | |
| 162 | def should_send_digest? |
| 163 | pending_notifications.count >= 5 || |
| 164 | oldest_pending_notification_age > 1.hour |
| 165 | end |
| 166 | |
| 167 | def send_digest |
| 168 | return unless should_send_digest? |
| 169 | notifications = pending_notifications |