Skip to content

Commit 18fc32f

Browse files
committed
Add CC (Carbon Copy) email notification functionality - #107
1 parent e62f76a commit 18fc32f

File tree

6 files changed

+738
-2
lines changed

6 files changed

+738
-2
lines changed
Lines changed: 396 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,396 @@
1+
# CC (Carbon Copy) Feature Implementation
2+
3+
## Overview
4+
5+
The CC (Carbon Copy) functionality has been added to the activity_notification gem's email notification system. This feature allows email notifications to be sent with additional CC recipients, following the same pattern as existing email header fields like `from`, `reply_to`, and `to`.
6+
7+
CC recipients can be configured at three levels:
8+
1. **Global configuration** - Set a default CC for all notifications via the gem's configuration file
9+
2. **Target model** - Define CC recipients at the target level (e.g., User, Admin)
10+
3. **Notifiable model** - Override CC per notification type in the notifiable model
11+
12+
## Implementation Details
13+
14+
### Files Modified
15+
16+
1. **lib/activity_notification/config.rb**
17+
- Added `mailer_cc` configuration attribute to allow global CC configuration
18+
- Supports String, Array, or Proc values for flexible CC recipient configuration
19+
20+
2. **lib/activity_notification/mailers/helpers.rb**
21+
- Added `cc: :mailer_cc` to the email headers processing loop in the `headers_for` method
22+
- Updated the `mailer_cc` helper method to check configuration when target doesn't define mailer_cc
23+
- Updated the header value resolution logic to properly handle the `mailer_cc` method which takes a target parameter instead of a key parameter
24+
25+
3. **lib/generators/templates/activity_notification.rb**
26+
- Added configuration example and documentation for `config.mailer_cc`
27+
28+
### Key Features
29+
30+
- **Three-Level Configuration**: CC can be configured at the global level (gem configuration), target level (model), or notification level (per-notification type)
31+
- **Flexible CC Recipients**: CC can be specified as a single email address (String), multiple email addresses (Array), or dynamic via Proc
32+
- **Optional Implementation**: All CC configuration is optional - if not defined, no CC recipients will be added
33+
- **Override Support**: Like other email headers, CC can be overridden per notification using the `overriding_notification_email_cc` method in the notifiable model
34+
- **Consistent Pattern**: Follows the same implementation pattern as existing email headers (`from`, `reply_to`, `to`)
35+
36+
## Usage Guide
37+
38+
### Method 1: Configure CC Globally (New Feature)
39+
40+
Set a default CC for all notification emails in your initializer:
41+
42+
```ruby
43+
# config/initializers/activity_notification.rb
44+
ActivityNotification.configure do |config|
45+
# Single CC recipient for all notifications
46+
config.mailer_cc = '[email protected]'
47+
48+
# OR multiple CC recipients
49+
config.mailer_cc = ['[email protected]', '[email protected]']
50+
51+
# OR dynamic CC based on notification key
52+
config.mailer_cc = ->(key) {
53+
if key.include?('urgent')
54+
55+
else
56+
57+
end
58+
}
59+
end
60+
```
61+
62+
### Method 2: Define `mailer_cc` in Your Target Model
63+
64+
Add a `mailer_cc` method to your target model (e.g., User, Admin) to specify CC recipients for that target. This overrides the global configuration:
65+
66+
```ruby
67+
class User < ApplicationRecord
68+
acts_as_target
69+
70+
# Return a single CC email address
71+
def mailer_cc
72+
73+
end
74+
75+
# OR return multiple CC email addresses
76+
def mailer_cc
77+
78+
end
79+
80+
# OR conditionally return CC addresses
81+
def mailer_cc
82+
return nil unless self.team_lead.present?
83+
self.team_lead.email
84+
end
85+
end
86+
```
87+
88+
### Method 3: Override CC Per Notification Type
89+
90+
For more granular control, implement `overriding_notification_email_cc` in your notifiable model to set CC based on the notification type. This has the highest priority:
91+
92+
```ruby
93+
class Article < ApplicationRecord
94+
acts_as_notifiable
95+
96+
def overriding_notification_email_cc(target, key)
97+
case key
98+
when 'article.commented'
99+
# CC the article author on comment notifications
100+
self.author.email
101+
when 'article.published'
102+
# CC multiple recipients for published articles
103+
104+
else
105+
nil # Use target's mailer_cc or global config
106+
end
107+
end
108+
end
109+
```
110+
111+
### Method 4: Combine All Approaches
112+
113+
You can combine all approaches - the priority order is: notification override > target method > global configuration:
114+
115+
```ruby
116+
# config/initializers/activity_notification.rb
117+
ActivityNotification.configure do |config|
118+
# Global default for all notifications
119+
config.mailer_cc = "[email protected]"
120+
end
121+
122+
class User < ApplicationRecord
123+
acts_as_target
124+
125+
# Override global config for this target
126+
def mailer_cc
127+
128+
end
129+
end
130+
131+
class Comment < ApplicationRecord
132+
acts_as_notifiable
133+
134+
# Override both global config and target method for specific notifications
135+
def overriding_notification_email_cc(target, key)
136+
if key == 'comment.urgent'
137+
138+
else
139+
nil # Falls back to target.mailer_cc, then global config
140+
end
141+
end
142+
end
143+
```
144+
145+
## Examples
146+
147+
### Example 1: Global Configuration with Static CC
148+
149+
```ruby
150+
# config/initializers/activity_notification.rb
151+
ActivityNotification.configure do |config|
152+
config.mailer_cc = "[email protected]"
153+
end
154+
155+
# All notification emails will include:
156+
157+
158+
```
159+
160+
### Example 2: Global Configuration with Multiple CC Recipients
161+
162+
```ruby
163+
# config/initializers/activity_notification.rb
164+
ActivityNotification.configure do |config|
165+
config.mailer_cc = ["[email protected]", "[email protected]"]
166+
end
167+
168+
# All notification emails will include:
169+
170+
171+
```
172+
173+
### Example 3: Dynamic Global CC Based on Notification Key
174+
175+
```ruby
176+
# config/initializers/activity_notification.rb
177+
ActivityNotification.configure do |config|
178+
config.mailer_cc = ->(key) {
179+
case key
180+
when /urgent/
181+
182+
when /comment/
183+
184+
else
185+
186+
end
187+
}
188+
end
189+
```
190+
191+
### Example 4: Target-Level Static CC
192+
193+
```ruby
194+
class User < ApplicationRecord
195+
acts_as_target
196+
197+
def mailer_cc
198+
199+
end
200+
end
201+
202+
# When a notification is sent, the email will include:
203+
204+
205+
```
206+
207+
### Example 5: Target-Level Multiple CC Recipients
208+
209+
```ruby
210+
class User < ApplicationRecord
211+
acts_as_target
212+
213+
def mailer_cc
214+
215+
end
216+
end
217+
218+
# Email will include:
219+
220+
221+
```
222+
223+
### Example 6: Dynamic CC Based on User Attributes
224+
225+
```ruby
226+
class User < ApplicationRecord
227+
acts_as_target
228+
belongs_to :department
229+
230+
def mailer_cc
231+
cc_list = []
232+
cc_list << self.manager.email if self.manager.present?
233+
cc_list << self.department.email if self.department.present?
234+
cc_list.presence # Returns nil if empty, otherwise returns the array
235+
end
236+
end
237+
```
238+
239+
### Example 7: Override CC Per Notification
240+
241+
```ruby
242+
class Article < ApplicationRecord
243+
acts_as_notifiable
244+
belongs_to :author
245+
246+
def overriding_notification_email_cc(target, key)
247+
case key
248+
when 'article.new_comment'
249+
# Notify the article author when someone comments
250+
self.author.email
251+
when 'article.shared'
252+
# Notify multiple stakeholders when article is shared
253+
[self.author.email, "[email protected]"]
254+
when 'article.flagged'
255+
# Notify moderation team
256+
257+
else
258+
nil
259+
end
260+
end
261+
end
262+
```
263+
264+
### Example 8: Conditional CC Based on Target and Key
265+
266+
```ruby
267+
class Post < ApplicationRecord
268+
acts_as_notifiable
269+
270+
def overriding_notification_email_cc(target, key)
271+
cc_list = []
272+
273+
# Always CC the post owner
274+
cc_list << self.user.email if self.user.present?
275+
276+
# For urgent notifications, CC administrators
277+
if key.include?('urgent')
278+
cc_list += User.where(role: 'admin').pluck(:email)
279+
end
280+
281+
# For specific users, CC their team lead
282+
if target.team_lead.present?
283+
cc_list << target.team_lead.email
284+
end
285+
286+
cc_list.uniq.presence
287+
end
288+
end
289+
```
290+
291+
## Technical Details
292+
293+
### Resolution Order
294+
295+
The CC recipient(s) are resolved in the following priority order:
296+
297+
1. **Override Method** (Highest Priority): If the notifiable model has `overriding_notification_email_cc(target, key)` defined and returns a non-nil value, that value is used
298+
2. **Target Method**: If no override is provided, the target's `mailer_cc` method is called (if it exists)
299+
3. **Global Configuration**: If the target doesn't have a `mailer_cc` method, the global `config.mailer_cc` setting is used (if configured)
300+
4. **No CC** (Default): If none of the above are defined or all return nil, no CC header is added to the email
301+
302+
### Return Value Format
303+
304+
Both the `mailer_cc` method and `config.mailer_cc` configuration can return:
305+
- **String**: A single email address (e.g., `"[email protected]"`)
306+
- **Array<String>**: Multiple email addresses (e.g., `["[email protected]", "[email protected]"]`)
307+
- **Proc**: A lambda/proc that takes the notification key and returns a String, Array, or nil (e.g., `->(key) { key.include?('urgent') ? '[email protected]' : nil }`)
308+
- **nil**: No CC recipients (CC header will not be added to the email)
309+
310+
### Implementation Pattern
311+
312+
The CC feature follows the same pattern as other email headers in the gem:
313+
314+
```ruby
315+
# In headers_for method
316+
{
317+
subject: :subject_for,
318+
from: :mailer_from,
319+
reply_to: :mailer_reply_to,
320+
cc: :mailer_cc, # <-- New CC support
321+
message_id: nil
322+
}.each do |header_name, default_method|
323+
# Check for override method in notifiable
324+
overridding_method_name = "overriding_notification_email_#{header_name}"
325+
if notifiable.respond_to?(overridding_method_name)
326+
use_override_value
327+
elsif default_method
328+
use_default_method
329+
end
330+
end
331+
```
332+
333+
## Testing
334+
335+
To test the CC functionality in your application:
336+
337+
```ruby
338+
# RSpec example
339+
RSpec.describe "Notification emails with CC" do
340+
let(:user) { create(:user) }
341+
let(:notification) { create(:notification, target: user) }
342+
343+
before do
344+
# Define mailer_cc for the test
345+
allow(user).to receive(:mailer_cc).and_return("[email protected]")
346+
end
347+
348+
it "includes CC recipient in email" do
349+
mail = ActivityNotification::Mailer.send_notification_email(notification)
350+
expect(mail.cc).to include("[email protected]")
351+
end
352+
353+
it "supports multiple CC recipients" do
354+
allow(user).to receive(:mailer_cc).and_return(["[email protected]", "[email protected]"])
355+
mail = ActivityNotification::Mailer.send_notification_email(notification)
356+
expect(mail.cc).to eq(["[email protected]", "[email protected]"])
357+
end
358+
359+
it "does not include CC header when nil" do
360+
allow(user).to receive(:mailer_cc).and_return(nil)
361+
mail = ActivityNotification::Mailer.send_notification_email(notification)
362+
expect(mail.cc).to be_nil
363+
end
364+
end
365+
```
366+
367+
## Backward Compatibility
368+
369+
This feature is **fully backward compatible**:
370+
- Existing applications without `mailer_cc` defined will continue to work exactly as before
371+
- No CC header will be added to emails unless explicitly configured
372+
- No database migrations or configuration changes are required
373+
- The implementation gracefully handles cases where `mailer_cc` is not defined
374+
375+
## Best Practices
376+
377+
1. **Return nil for no CC**: If you don't want CC recipients, return `nil` rather than an empty array or empty string
378+
2. **Validate email addresses**: Ensure CC recipients are valid email addresses to avoid mail delivery issues
379+
3. **Avoid excessive CC**: Be mindful of privacy and avoid CCing too many recipients
380+
4. **Use override for specific cases**: Use `overriding_notification_email_cc` for notification-specific CC logic
381+
5. **Keep it simple**: Use the target's `mailer_cc` method for consistent CC across all notifications
382+
383+
## Related Methods
384+
385+
The CC feature works alongside these existing email configuration methods:
386+
387+
- `mailer_to` - Primary recipient email address (required)
388+
- `mailer_from` - Sender email address
389+
- `mailer_reply_to` - Reply-to email address
390+
- `mailer_cc` - Carbon copy recipients (new)
391+
392+
All of these can be overridden using the `overriding_notification_email_*` pattern in the notifiable model.
393+
394+
## Summary
395+
396+
The CC functionality seamlessly integrates with the existing activity_notification email system, providing a flexible and powerful way to add carbon copy recipients to notification emails. Whether you need static CC addresses, dynamic recipients based on user attributes, or notification-specific CC logic, this implementation supports all these use cases while maintaining backward compatibility with existing code.

0 commit comments

Comments
 (0)