email_digest 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/.vscode/settings.json +3 -0
  3. data/ANSWERS_TO_YOUR_QUESTIONS.md +361 -0
  4. data/CHANGELOG.md +14 -0
  5. data/COMPLETE_SETUP_INSTRUCTIONS.md +298 -0
  6. data/CUSTOMIZATION_GUIDE.md +264 -0
  7. data/FAQ.md +133 -0
  8. data/LICENSE.txt +21 -0
  9. data/QUICK_START.md +62 -0
  10. data/README.md +253 -0
  11. data/REPOSITORY_READY.md +146 -0
  12. data/RUBYGEMS_PUBLISHING.md +272 -0
  13. data/SETUP_SEPARATE_GEM_REPO.md +131 -0
  14. data/email_digest.gemspec +52 -0
  15. data/lib/email_digest/concerns/digestable.rb +57 -0
  16. data/lib/email_digest/configuration.rb +40 -0
  17. data/lib/email_digest/generators/email_digest/install/install_generator.rb +50 -0
  18. data/lib/email_digest/generators/email_digest/install/templates/README +16 -0
  19. data/lib/email_digest/generators/email_digest/install/templates/create_email_digest_tables.rb +77 -0
  20. data/lib/email_digest/generators/email_digest/install/templates/email_digest.rb +25 -0
  21. data/lib/email_digest/models/digest_item.rb +49 -0
  22. data/lib/email_digest/models/digest_preference.rb +106 -0
  23. data/lib/email_digest/railtie.rb +9 -0
  24. data/lib/email_digest/services/digest_collector.rb +38 -0
  25. data/lib/email_digest/services/digest_scheduler.rb +55 -0
  26. data/lib/email_digest/services/digest_summarizer.rb +135 -0
  27. data/lib/email_digest/version.rb +5 -0
  28. data/lib/email_digest/workers/digest_processor_worker.rb +64 -0
  29. data/lib/email_digest/workers/digest_scheduler_worker.rb +26 -0
  30. data/lib/email_digest/workers/digest_sender_worker.rb +48 -0
  31. data/lib/email_digest.rb +37 -0
  32. metadata +241 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e5ba6e19b8f7962fc7f96d1623e403bbff8b9e60957a209c50fc873eb4f391ca
4
+ data.tar.gz: e5a6f65646956c66671bdba5c454e250806a520d62912bf21c1bcf38309aeff3
5
+ SHA512:
6
+ metadata.gz: 55f42707c03303f309ed0850f0417237e155a5ce74eb7729775cc4c88bb289c3003d730e36515cdc1195cc9ca238f9c13a64f85f3d9ab1e8565c88959833eb17
7
+ data.tar.gz: c5316893ee2b57debaf88311305a095b769006872c1981f979187d9663935565db056b221af9a32f3d6dbafda58bc0ff2112bb30ee120c220262f37d1b55ac6a
@@ -0,0 +1,3 @@
1
+ {
2
+ "snyk.advanced.autoSelectOrganization": true
3
+ }
@@ -0,0 +1,361 @@
1
+ # Answers to Your Questions
2
+
3
+ ## Q1: Will it trigger if only 1 digest_item is present?
4
+
5
+ **✅ YES!** The system triggers if there are **any items** (1 or more).
6
+
7
+ Looking at `digest_scheduler.rb` line 39:
8
+ ```ruby
9
+ if items.any? # This means 1 or more items will trigger
10
+ Workers::DigestProcessorWorker.perform_async(...)
11
+ end
12
+ ```
13
+
14
+ **Answer:** Even a single digest item will trigger processing and email sending.
15
+
16
+ ---
17
+
18
+ ## Q2: What context is being sent to users in mail? Can I customize it per digest_type?
19
+
20
+ **Current Context:**
21
+ The `DigestSummarizer` creates a summary hash with:
22
+ - `subject` - Email subject line
23
+ - `summary` - Summary text
24
+ - `items` - Array of all items
25
+ - `grouped_items` - Items grouped by notification_type
26
+ - `total_count` - Total number of items
27
+
28
+ Each item includes:
29
+ - `id`, `notification_type`, `subject`, `body`
30
+ - `metadata` - JSONB field (can store anything)
31
+ - `source_id`, `source_type`, `priority`, `created_at`
32
+
33
+ **✅ YES, you can customize it per digest_type at your code level!**
34
+
35
+ **Solution:** Create custom summarizer classes in your application:
36
+
37
+ ```ruby
38
+ # app/services/email_digest/summarizers/time_log_summarizer.rb
39
+ module EmailDigest
40
+ module Summarizers
41
+ class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
42
+ # Override methods to customize the summary
43
+ def single_item_summary
44
+ item = items.first
45
+ item_hash = enrich_item_hash(item_to_hash(item), item)
46
+
47
+ {
48
+ subject: "Custom Subject: #{item.subject}",
49
+ summary: "Custom summary text",
50
+ items: [item_hash],
51
+ grouped_items: { item.notification_type => [item_hash] },
52
+ total_count: 1,
53
+ # Add your custom fields here
54
+ custom_context: {
55
+ course: item_hash[:course],
56
+ section: item_hash[:section]
57
+ }
58
+ }
59
+ end
60
+
61
+ def enrich_item_hash(item_hash, item)
62
+ metadata = item.metadata || {}
63
+ item_hash.merge(
64
+ course: load_course(metadata['course_id']),
65
+ section: load_section(metadata['section_id']),
66
+ group_template: load_group_template(metadata['group_template_id']),
67
+ group: load_group(metadata['group_id'])
68
+ )
69
+ end
70
+
71
+ private
72
+
73
+ def load_course(course_id)
74
+ return nil unless course_id
75
+ Course.find(course_id) rescue nil
76
+ end
77
+ # ... other load methods
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ Then register it:
84
+ ```ruby
85
+ # config/initializers/email_digest.rb
86
+ EmailDigest.configure do |config|
87
+ config.register_digest_type(:time_log_notifications, {
88
+ mailer_method: 'time_log_digest_email',
89
+ summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer' # Your custom class
90
+ })
91
+ end
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Q3: How are different digest_types, users, and organizations combined? What runs at 15 mins?
97
+
98
+ **How it works:**
99
+
100
+ 1. **Every 15 minutes**, `DigestSchedulerWorker` runs (configured in `sidekiq_cron_schedule.yml`)
101
+
102
+ 2. It calls `DigestScheduler.process_all_ready(organization)` which:
103
+ - Finds all `DigestPreference` records where:
104
+ - `next_send_at <= now`
105
+ - `enabled = true`
106
+ - Optionally filtered by `organization_id`
107
+
108
+ 3. **Each preference is unique per `(user_id, digest_type, organization_id)`**
109
+
110
+ 4. For each preference:
111
+ - Uses `DigestCollector` to gather pending items for that specific user+digest_type+organization
112
+ - If items exist (1+), processes them together
113
+ - Sends **one email per preference**
114
+
115
+ **Important:** Items are **NOT combined** across:
116
+ - Different digest_types
117
+ - Different users
118
+ - Different organizations
119
+
120
+ **Example:**
121
+ - User A with `time_log_notifications` → Gets one email
122
+ - User A with `mentor_supervisor_notifications` → Gets a separate email
123
+ - User B with `time_log_notifications` → Gets a separate email
124
+
125
+ ---
126
+
127
+ ## Q4: How to store additional information (course, section, groupTemplate, group) in digest_item?
128
+
129
+ **✅ Use the `metadata` field (JSONB)!**
130
+
131
+ The `metadata` field can store any additional context you need:
132
+
133
+ ```ruby
134
+ # When creating digest items
135
+ EmailDigest::Models::DigestItem.create_for_user(
136
+ user,
137
+ :time_log_notifications,
138
+ :submission_received,
139
+ subject: "New Time Log Submission",
140
+ body: "A new time log has been submitted.",
141
+ metadata: {
142
+ course_id: course.id.to_s,
143
+ course_name: course.name,
144
+ section_id: section.id.to_s,
145
+ section_name: section.name,
146
+ group_template_id: group_template.id.to_s,
147
+ group_template_name: group_template.name,
148
+ group_id: group.id.to_s,
149
+ group_name: group.name,
150
+ student_name: student.name,
151
+ # ... any other data you need
152
+ }
153
+ )
154
+ ```
155
+
156
+ **Then in your custom summarizer**, access and enrich this data:
157
+
158
+ ```ruby
159
+ def enrich_item_hash(item_hash, item)
160
+ metadata = item.metadata || {}
161
+ item_hash.merge(
162
+ course: {
163
+ id: metadata['course_id'],
164
+ name: metadata['course_name']
165
+ },
166
+ section: {
167
+ id: metadata['section_id'],
168
+ name: metadata['section_name']
169
+ },
170
+ group_template: {
171
+ id: metadata['group_template_id'],
172
+ name: metadata['group_template_name']
173
+ },
174
+ group: {
175
+ id: metadata['group_id'],
176
+ name: metadata['group_name']
177
+ }
178
+ )
179
+ end
180
+ ```
181
+
182
+ **Or load full objects:**
183
+
184
+ ```ruby
185
+ def enrich_item_hash(item_hash, item)
186
+ metadata = item.metadata || {}
187
+ item_hash.merge(
188
+ course: load_course(metadata['course_id']),
189
+ section: load_section(metadata['section_id']),
190
+ group_template: load_group_template(metadata['group_template_id']),
191
+ group: load_group(metadata['group_id'])
192
+ )
193
+ end
194
+
195
+ private
196
+
197
+ def load_course(course_id)
198
+ return nil unless course_id
199
+ Course.find(course_id) rescue nil
200
+ end
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Complete Example: Time Log Digest with Course/Section Context
206
+
207
+ ### 1. Create Digest Item with Metadata
208
+
209
+ ```ruby
210
+ # In your application code
211
+ time_log.add_to_digest(
212
+ instructor,
213
+ :submission_received,
214
+ subject: "New Time Log Submission",
215
+ body: "A new time log has been submitted for review.",
216
+ priority: 5,
217
+ metadata: {
218
+ course_id: time_log.course.id.to_s,
219
+ course_name: time_log.course.name,
220
+ section_id: time_log.section.id.to_s,
221
+ section_name: time_log.section.name,
222
+ group_template_id: time_log.group_template&.id&.to_s,
223
+ group_id: time_log.group.id.to_s,
224
+ group_name: time_log.group.name,
225
+ student_id: time_log.student.id.to_s,
226
+ student_name: time_log.student.name
227
+ }
228
+ )
229
+ ```
230
+
231
+ ### 2. Create Custom Summarizer
232
+
233
+ ```ruby
234
+ # app/services/email_digest/summarizers/time_log_summarizer.rb
235
+ module EmailDigest
236
+ module Summarizers
237
+ class TimeLogSummarizer < EmailDigest::Services::DigestSummarizer
238
+ def enrich_item_hash(item_hash, item)
239
+ metadata = item.metadata || {}
240
+
241
+ # Load full objects from metadata
242
+ item_hash.merge(
243
+ course: load_course(metadata['course_id']),
244
+ section: load_section(metadata['section_id']),
245
+ group_template: load_group_template(metadata['group_template_id']),
246
+ group: load_group(metadata['group_id']),
247
+ student: load_student(metadata['student_id']),
248
+ # Include metadata for easy access
249
+ course_name: metadata['course_name'],
250
+ section_name: metadata['section_name'],
251
+ group_name: metadata['group_name'],
252
+ student_name: metadata['student_name']
253
+ )
254
+ end
255
+
256
+ def grouped_summary
257
+ grouped = items.group_by do |item|
258
+ # Group by course and section
259
+ metadata = item.metadata || {}
260
+ "#{metadata['course_name']} - #{metadata['section_name']}"
261
+ end
262
+
263
+ items_hash = items.map { |item| enrich_item_hash(item_to_hash(item), item) }
264
+ grouped_hash = grouped.transform_values do |items|
265
+ items.map { |item| enrich_item_hash(item_to_hash(item), item) }
266
+ end
267
+
268
+ {
269
+ subject: "Time Log Notifications - #{items.size} New Submissions",
270
+ summary: "You have #{items.size} new time log submissions across #{grouped.size} courses.",
271
+ items: items_hash,
272
+ grouped_items: grouped_hash,
273
+ total_count: items.size,
274
+ grouped: true
275
+ }
276
+ end
277
+
278
+ private
279
+
280
+ def load_course(course_id)
281
+ return nil unless course_id
282
+ Course.find(course_id) rescue nil
283
+ end
284
+
285
+ def load_section(section_id)
286
+ return nil unless section_id
287
+ Section.find(section_id) rescue nil
288
+ end
289
+
290
+ def load_group_template(group_template_id)
291
+ return nil unless group_template_id
292
+ GroupTemplate.find(group_template_id) rescue nil
293
+ end
294
+
295
+ def load_group(group_id)
296
+ return nil unless group_id
297
+ Group.find(group_id) rescue nil
298
+ end
299
+
300
+ def load_student(student_id)
301
+ return nil unless student_id
302
+ Student.find(student_id) rescue nil
303
+ end
304
+ end
305
+ end
306
+ end
307
+ ```
308
+
309
+ ### 3. Register Custom Summarizer
310
+
311
+ ```ruby
312
+ # config/initializers/email_digest.rb
313
+ EmailDigest.configure do |config|
314
+ config.register_digest_type(:time_log_notifications, {
315
+ mailer_method: 'time_log_digest_email',
316
+ summarizer: 'EmailDigest::Summarizers::TimeLogSummarizer'
317
+ })
318
+ end
319
+ ```
320
+
321
+ ### 4. Use in Mailer Template
322
+
323
+ ```erb
324
+ <!-- app/views/user_mailer/time_log_digest_email.html.erb -->
325
+ <% @summary[:grouped_items].each do |group_key, items| %>
326
+ <h2><%= group_key %></h2>
327
+ <% items.each do |item| %>
328
+ <div>
329
+ <h3><%= item[:subject] %></h3>
330
+ <p><%= item[:body] %></p>
331
+
332
+ <% if item[:course_name] %>
333
+ <p><strong>Course:</strong> <%= item[:course_name] %></p>
334
+ <% end %>
335
+
336
+ <% if item[:section_name] %>
337
+ <p><strong>Section:</strong> <%= item[:section_name] %></p>
338
+ <% end %>
339
+
340
+ <% if item[:group_name] %>
341
+ <p><strong>Group:</strong> <%= item[:group_name] %></p>
342
+ <% end %>
343
+
344
+ <% if item[:student_name] %>
345
+ <p><strong>Student:</strong> <%= item[:student_name] %></p>
346
+ <% end %>
347
+ </div>
348
+ <% end %>
349
+ <% end %>
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Summary
355
+
356
+ ✅ **Q1:** Yes, triggers with 1+ items
357
+ ✅ **Q2:** Customizable per digest_type via custom summarizers in your app
358
+ ✅ **Q3:** Each email is for one user + one digest_type + one organization, processed every 15 mins
359
+ ✅ **Q4:** Store additional info in `metadata` JSONB field, enrich in custom summarizer
360
+
361
+ See `CUSTOMIZATION_GUIDE.md` for more detailed examples!
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [1.0.0] - 2025-01-27
6
+
7
+ ### Added
8
+ - Initial release
9
+ - PostgreSQL backend
10
+ - Sidekiq integration
11
+ - Multi-tenant support
12
+ - Flexible scheduling
13
+ - Priority-based processing
14
+ - Smart summarization
@@ -0,0 +1,298 @@
1
+ # Complete Setup Instructions for EmailDigest Gem
2
+
3
+ ## Overview
4
+
5
+ This guide will help you create a completely separate gem repository that you can:
6
+ 1. Use in your application via Gemfile
7
+ 2. Publish to RubyGems for others to use
8
+ 3. Maintain independently from your main application
9
+
10
+ ## Step-by-Step Instructions
11
+
12
+ ### Step 1: Create New Repository Directory
13
+
14
+ ```bash
15
+ # Navigate to a location outside your main app
16
+ cd ~/workspace # or ~/projects, or wherever you prefer
17
+
18
+ # Create new directory
19
+ mkdir email_digest
20
+ cd email_digest
21
+
22
+ # Initialize git
23
+ git init
24
+ ```
25
+
26
+ ### Step 2: Copy All Gem Files
27
+
28
+ ```bash
29
+ # Copy everything from gem_package
30
+ cp -r /Users/rosharma/workspace/sll-la/eportfolio/gem_package/* ~/workspace/email_digest/
31
+
32
+ # Verify files are copied
33
+ ls -la
34
+ # You should see: email_digest.gemspec, lib/, README.md, etc.
35
+ ```
36
+
37
+ ### Step 3: Update Gemspec Metadata
38
+
39
+ Edit `email_digest.gemspec`:
40
+
41
+ ```ruby
42
+ spec.authors = ['Your Name'] # Change this
43
+ spec.email = ['your.email@example.com'] # Change this
44
+ spec.homepage = 'https://github.com/yourusername/email_digest' # Change this
45
+ spec.metadata['source_code_uri'] = 'https://github.com/yourusername/email_digest' # Change this
46
+ ```
47
+
48
+ ### Step 4: Create GitHub Repository
49
+
50
+ #### Option A: Via GitHub Web Interface
51
+
52
+ 1. Go to https://github.com/new
53
+ 2. Repository name: `email_digest`
54
+ 3. Description: "A flexible email digest system for Rails"
55
+ 4. Choose Public or Private
56
+ 5. Don't initialize with README (you already have one)
57
+ 6. Click "Create repository"
58
+
59
+ #### Option B: Via GitHub CLI
60
+
61
+ ```bash
62
+ gh repo create email_digest --public --description "A flexible email digest system for Rails"
63
+ ```
64
+
65
+ ### Step 5: Connect and Push to GitHub
66
+
67
+ ```bash
68
+ # Add remote
69
+ git remote add origin https://github.com/yourusername/email_digest.git
70
+
71
+ # Add all files
72
+ git add .
73
+
74
+ # Initial commit
75
+ git commit -m "Initial commit: EmailDigest gem v1.0.0"
76
+
77
+ # Push to GitHub
78
+ git branch -M main
79
+ git push -u origin main
80
+ ```
81
+
82
+ ### Step 6: Test the Gem Locally
83
+
84
+ ```bash
85
+ # Build the gem
86
+ gem build email_digest.gemspec
87
+
88
+ # This creates: email_digest-1.0.0.gem
89
+
90
+ # Test installation
91
+ gem install ./email_digest-1.0.0.gem
92
+
93
+ # Or test in your app
94
+ # In your app's Gemfile, add:
95
+ # gem 'email_digest', path: '../email_digest'
96
+ # Then: bundle install
97
+ ```
98
+
99
+ ### Step 7: Publish to RubyGems
100
+
101
+ #### 7a. Create RubyGems Account
102
+
103
+ 1. Go to https://rubygems.org
104
+ 2. Click "Sign up"
105
+ 3. Fill in your details
106
+ 4. Verify your email
107
+
108
+ #### 7b. Get API Key
109
+
110
+ 1. Go to https://rubygems.org/profile/edit
111
+ 2. Scroll to "API Access"
112
+ 3. Click "Add a new API key"
113
+ 4. Give it a name (e.g., "laptop")
114
+ 5. Copy the key (you'll use it once)
115
+
116
+ #### 7c. Configure Credentials
117
+
118
+ ```bash
119
+ # Option 1: Use gem signin
120
+ gem signin
121
+ # Enter your RubyGems username and password
122
+
123
+ # Option 2: Manual setup
124
+ # Create ~/.gem/credentials with:
125
+ # ---
126
+ # :rubygems_api_key: YOUR_API_KEY_HERE
127
+ ```
128
+
129
+ #### 7d. Publish
130
+
131
+ ```bash
132
+ # Make sure you've built the gem
133
+ gem build email_digest.gemspec
134
+
135
+ # Push to RubyGems
136
+ gem push email_digest-1.0.0.gem
137
+
138
+ # You should see:
139
+ # Pushing gem to https://rubygems.org...
140
+ # Successfully registered gem: email_digest
141
+ ```
142
+
143
+ #### 7e. Verify
144
+
145
+ ```bash
146
+ # Check on RubyGems
147
+ open https://rubygems.org/gems/email_digest
148
+
149
+ # Or via command line
150
+ gem search email_digest
151
+ gem info email_digest
152
+ ```
153
+
154
+ ### Step 8: Use in Your Application
155
+
156
+ In your main app's `Gemfile`:
157
+
158
+ ```ruby
159
+ # From RubyGems (recommended)
160
+ gem 'email_digest'
161
+
162
+ # Or from GitHub (for development)
163
+ gem 'email_digest', git: 'https://github.com/yourusername/email_digest.git'
164
+
165
+ # Or local path (for local development)
166
+ gem 'email_digest', path: '../email_digest'
167
+ ```
168
+
169
+ Then in your app:
170
+
171
+ ```bash
172
+ bundle install
173
+ rails generate email_digest:install
174
+ rails db:migrate
175
+ ```
176
+
177
+ ## File Structure
178
+
179
+ Your gem repository should have:
180
+
181
+ ```
182
+ email_digest/
183
+ ├── .gitignore
184
+ ├── .github/
185
+ │ └── workflows/
186
+ │ └── ci.yml
187
+ ├── email_digest.gemspec
188
+ ├── README.md
189
+ ├── LICENSE.txt
190
+ ├── CHANGELOG.md
191
+ ├── RUBYGEMS_PUBLISHING.md
192
+ └── lib/
193
+ ├── email_digest.rb
194
+ └── email_digest/
195
+ ├── version.rb
196
+ ├── configuration.rb
197
+ ├── railtie.rb
198
+ ├── models/
199
+ │ ├── digest_item.rb
200
+ │ └── digest_preference.rb
201
+ ├── services/
202
+ │ ├── digest_collector.rb
203
+ │ ├── digest_summarizer.rb
204
+ │ └── digest_scheduler.rb
205
+ ├── workers/
206
+ │ ├── digest_processor_worker.rb
207
+ │ ├── digest_sender_worker.rb
208
+ │ └── digest_scheduler_worker.rb
209
+ ├── concerns/
210
+ │ └── digestable.rb
211
+ └── generators/
212
+ └── email_digest/
213
+ └── install/
214
+ ├── install_generator.rb
215
+ └── templates/
216
+ ├── email_digest.rb
217
+ ├── create_email_digest_tables.rb
218
+ └── README
219
+ ```
220
+
221
+ ## Updating the Gem
222
+
223
+ When you make changes:
224
+
225
+ ```bash
226
+ # 1. Update version in lib/email_digest/version.rb
227
+ # 2. Update CHANGELOG.md
228
+ # 3. Commit changes
229
+ git add .
230
+ git commit -m "Update: [description]"
231
+ git push
232
+
233
+ # 4. Tag the release
234
+ git tag -a v1.0.1 -m "Version 1.0.1"
235
+ git push origin v1.0.1
236
+
237
+ # 5. Build and publish
238
+ gem build email_digest.gemspec
239
+ gem push email_digest-1.0.1.gem
240
+ ```
241
+
242
+ ## Quick Reference
243
+
244
+ ### Local Development
245
+
246
+ ```bash
247
+ # In your app's Gemfile
248
+ gem 'email_digest', path: '../email_digest'
249
+ bundle install
250
+ ```
251
+
252
+ ### From GitHub
253
+
254
+ ```bash
255
+ # In your app's Gemfile
256
+ gem 'email_digest', git: 'https://github.com/yourusername/email_digest.git'
257
+ bundle install
258
+ ```
259
+
260
+ ### From RubyGems
261
+
262
+ ```bash
263
+ # In your app's Gemfile
264
+ gem 'email_digest'
265
+ bundle install
266
+ ```
267
+
268
+ ## Troubleshooting
269
+
270
+ ### "Could not find gem"
271
+
272
+ - Make sure the path is correct
273
+ - Run `bundle install` again
274
+ - Check `bundle show email_digest` to verify location
275
+
276
+ ### "Generator not found"
277
+
278
+ - Make sure you ran `bundle install`
279
+ - Check that the gem is in your Gemfile
280
+ - Verify the gem path is correct
281
+
282
+ ### "Table already exists"
283
+
284
+ - You might have run the migration manually
285
+ - Check your database for `email_digest_items` and `email_digest_preferences` tables
286
+
287
+ ## Next Steps
288
+
289
+ 1. ✅ Create separate repo (done via this guide)
290
+ 2. ✅ Publish to RubyGems (follow Step 7)
291
+ 3. ✅ Use in your app (follow Step 8)
292
+ 4. ✅ Share with others (they can use `gem 'email_digest'`)
293
+
294
+ ## Support
295
+
296
+ - See `RUBYGEMS_PUBLISHING.md` for detailed publishing guide
297
+ - See `README.md` for usage documentation
298
+ - Check GitHub Issues for known problems