-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mailchimp unsubscribe webhook #5804
Mailchimp unsubscribe webhook #5804
Conversation
class Webhooks::MailchimpUnsubscribesController < ApplicationController | ||
class InvalidListID < StandardError; end | ||
|
||
LIST_MAPPINGS = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way to do this lookup/is this mapping defined somewhere already?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the first of it's kind 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, good to know. Can you please sanity check the mapping?
|
||
def create | ||
not_authorized unless valid_secret? | ||
user = User.find_by!(email: params.dig(:data, :email)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to raise in case we can't find the user, but I'm open to other approaches (log to DataDog and no-op for example).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fine for the time being. It should be caught by honeybadger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should not raise while still testing this out/debugging, because if my assumption about the webhook payload are wrong we'll raise for no good reason.
@@ -154,11 +154,15 @@ | |||
resources :reads, only: [:create] | |||
end | |||
|
|||
namespace :webhooks do | |||
post "/mailchimp/:secret/unsubscribe", to: "mailchimp_unsubscribes#create", as: :mailchimp_unsubscribe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably change this at one point and use routing DSL trickery, but for now this seems good enough.
73b4bab
to
158fe52
Compare
I think this is a very solid first iteration |
In light of this I removed the WIP tag now. Just let me know what we should regarding the exception if we can't find the user (I left a comment), then we could take this for a spin. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not 100% sure we should these things webhooks as it maybe confusing with user registered webhooks for outgoing events but I also don't have a better name 🗡
Good job @citizen428
@@ -0,0 +1,30 @@ | |||
class Webhooks::MailchimpUnsubscribesController < ApplicationController | |||
class InvalidListID < StandardError; end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should all this logic (the mappings and the email_type
) be in the controller?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is a POC, I opted for "easy to remove again" as my optimization target. If this sticks around, we can always move it out later. But if you prefer I can also move it out now, but since this mapping isn't used elsewhere yet, I don't have a problem with it being in this controller for the time being.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, it makes sense, let's leave it as is
How about |
Great idea! |
Changed back to WIP as I'll do some renaming tomorrow. |
@rhymes @maestromac I renamed this to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good @citizen428!
What are the next steps when this is merged?
RSpec.describe "IncomingWebhooks::MailchimpUnsubscribesController", type: :request do | ||
let(:user) { create(:user, email_digest_periodic: true) } | ||
|
||
describe "POST /webhooks/mailchimp/:secret/unsubscribe" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's a typo here but I don't think it should hold the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that it should say incoming_webhooks
? Because I don't really see a typo, but that could also be because I knew what I wanted to write 😉
mailchimp_sustaining_members_id: :email_membership_newsletter, | ||
mailchimp_tag_moderators_id: :email_tag_mod_newsletter, | ||
mailchimp_community_moderators_id: :email_community_mod_newsletter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from what i can tell, we're not using most of these keys; what do we need them for? (just trying to get context on this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the mapping of option names in the SiteConfig
model to newsletter types. We're using it here:
list_id = params.dig(:data, :list_id)
key = LIST_MAPPINGS.keys.detect { |k| SiteConfig.public_send(k) == list_id }
Once we have mapped from list id to attribute name, we use the latter in the update user.update(email_type => false)
.
Does that clear it up?
|
||
expect do | ||
post "/incoming_webhooks/mailchimp/#{secret}/unsubscribe", params: params | ||
end.to change { user.reload.email_newsletter }.from(true).to(false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, i didn't know about the from(true).to(false)
syntax! that's so much nicer than asserting the value of email_newsletter
on the user
before making the post request, and then checking it again afterwards!! ✨
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also by
, which can specify an amount of change, i.e.
expect do
User.create(...)
end.to change(User, :count).by(1)
@rhymes The next step would be to use @maestromac's account to subscribe/unsubscribe and confirm some of my assumptions around the payload shape etc. |
What type of PR is this? (check all applicable)
Description
When user's directly unsubscribe from Mailchimp, we don't have any record of that and keep sending emails. This PR adds a webhook endpoint so we get notified of unsubscribe events (docs.
Related Tickets & Documents
https://github.com/thepracticaldev/tech-private/issues/289
Mobile & Desktop Screenshots/Recordings (if there are UI changes)
Added to documentation?