Design: SendGrid batch email migration for workshop invitations#2591
Design: SendGrid batch email migration for workshop invitations#2591mroderick wants to merge 1 commit into
Conversation
633584e to
7305631
Compare
7305631 to
e29200f
Compare
|
|
||
| ## 2. Data Model | ||
|
|
||
| ### `sendgrid_migration_configs` (temporary) |
There was a problem hiding this comment.
So I would create templates on Sendgrid and reference them, e.g. one for students and one for coaches. No chapter specific templates.
| |---------------|----------|--------------|-------------------------| | ||
| | `id` | serial | PK | | | ||
| | `workshop_id` | integer | FK, not null | | | ||
| | `member_id` | integer | FK, not null | | |
There was a problem hiding this comment.
Not sure this is needed, as SendGrid will keep track of this for us.
| .find_by(chapter: workshop.chapter, email_type: "invite_student") | ||
| ``` | ||
|
|
||
| ### `sendgrid_deliveries` (temporary) |
There was a problem hiding this comment.
This table is not temporary.
| | `member_id` | integer | FK, not null | | | ||
| | `email_type` | string | not null | | | ||
| | `sg_batch_id` | string | | SendGrid `x-message-id` | | ||
| | `sent_at` | datetime | not null | | |
There was a problem hiding this comment.
Standardize filenames on created_at vs. domain specifics.
| | `sg_batch_id` | string | | SendGrid `x-message-id` | | ||
| | `sent_at` | datetime | not null | | | ||
|
|
||
| **Index:** `workshop_id + member_id + email_type` (unique) |
|
|
||
| **Index:** `workshop_id + member_id + email_type` (unique) | ||
|
|
||
| ### Whitelist: Rolify role |
|
|
||
| log_response(response, batch, batch_number, Time.current - start_time) | ||
| response | ||
| rescue Net::OpenTimeout, Net::ReadTimeout => e |
There was a problem hiding this comment.
Consider catching/rescue more broad error, not sure what else there is.
|
|
||
| **Search:** | ||
| ```bash | ||
| heroku logs --app codebar | grep "correlation_id=abc-123-def" |
There was a problem hiding this comment.
I am assuming we should log sendgrid's batch ID instead.
| def initialize(workshop, email_type) | ||
| @workshop = workshop | ||
| @email_type = email_type | ||
| @correlation_id = SecureRandom.uuid |
There was a problem hiding this comment.
Maybe instead of creating a unique ID here, we can re-use the Sendgrid batch ID for tracking. Also double-check if this actually needs to be injected back into the email customisations.
| - Records `sendgrid_deliveries` on 202 | ||
| - Is safe to retry | ||
|
|
||
| Use WebMock to stub SendGrid API. |
There was a problem hiding this comment.
Consider re-using the official test mocks:
https://github.com/sendgrid/ruby-http-client/blob/main/test/test_ruby_http_client.rb
|
Thank you for the feedback @till. I think I need to go back to the drawing board on this one, to make better use of the API like we discussed in our call. |
Summary
Design document for migrating workshop invitation emails from ActionMailer/SMTP to SendGrid's v3 batch API with dynamic templates.
Problem
Bulk workshop invitations currently exceed the 9-minute DelayedJob timeout for large chapters, causing jobs to be killed mid-send.
Solution
mail/sendAPI (up to 1,000 recipients per call)sendgrid_migration_configstable:sendgrid_betarolesendgrid_deliveriestable prevents duplicate sends on retryDocuments
docs/superpowers/specs/2026-04-24-sendgrid-batch-email-design.md— design specSendGrid Documentation
personalizationsScope
This PR contains only design documentation. Implementation will follow in a separate PR.