# Patreon v2 sync reports "N pledges, 0 users synced" — member email read from wrong resource in extract

**URL:** https://meta.discourse.org/t/patreon-v2-sync-reports-n-pledges-0-users-synced-member-email-read-from-wrong-resource-in-extract/411874
**Category:** Bug
**Tags:** patreon
**Created:** [September 8, 2026, 8:17am UTC](https://meta.discourse.org/t/patreon-v2-sync-reports-n-pledges-0-users-synced-member-email-read-from-wrong-resource-in-extract/411874 "2026-09-08T08:17:12Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![WaitroseCarpark](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/waitrosecarpark/32/198599_2.png) [@WaitroseCarpark](https://meta.discourse.org/u/WaitroseCarpark)
#### Post date: [September 8, 2026, 8:17am UTC](https://meta.discourse.org/t/patreon-v2-sync-reports-n-pledges-0-users-synced-member-email-read-from-wrong-resource-in-extract/411874/1 "2026-09-08T08:17:13Z")

</div>

Disclaimer: Yes I used Claude to help me fix this issue:

Running Discourse core at commit `5ee8e24` (2026-08-19). The Patreon sync completes without error but syncs zero users:

```plaintext
Patreon sync complete: 63 pledges, 0 users synced

```

API responses are all `status=200`, so the requests themselves are fine. Tracing it in the rails console, the members request the plugin builds is correct and does ask for email on the member resource:

```plaintext
fields[member]=full_name,last_charge_date,last_charge_status,currently_entitled_amount_cents,patron_status,email

```

Calling that endpoint directly with a properly scoped creator token (`campaigns.members[email]` granted), the email is present on the `member` object, not on the `user` object in `included`. A single-member fetch confirms it:

```plaintext
data.attributes.email => "patron@example.com" # present
included[].attributes.email => (absent) # user objects carry full_name only

```

So on the campaign members list, `member.attributes.email` is populated (46 of my 63 members; the rest have restricted sharing or are free followers), while the `included` user entries have no email field at all.

The problem is in `Patreon::ApiVersion::V2.extract` (`lib/api_version/v2.rb`). It builds the `users` map from the `included` user objects:

ruby

```plaintext
(member_data["included"] || []).each do |entry|
  if entry["type"] == "user" && entry["attributes"]["email"].present?
    users[entry["id"]] = entry["attributes"]["email"].downcase
  end
end

```

Because those user objects never carry email, the map comes out empty and every member fails email matching, hence 0 synced.

Reading email from the member entry instead (keyed by the user id already available in `member.relationships.user.data.id`, which is the same `patron_id` used for pledges) fixed it on my install: `0` became `46 users synced`. The change I applied:

diff

```plaintext
+ users[patron_id] = attrs["email"].downcase if attrs["email"].present?
             pledges[patron_id] = attrs["currently_entitled_amount_cents"]
             declines[patron_id] = attrs["last_charge_date"] if attrs["last_charge_status"] == "Declined"
           end
-
- (member_data["included"] || []).each do |entry|
- if entry["type"] == "user" && entry["attributes"]["email"].present?
- users[entry["id"]] = entry["attributes"]["email"].downcase
- end
- end

```

I’m not certain removing the `included` loop entirely is the right call for every case — if some setups do receive email on the user object, you may want to keep it as a fallback rather than replace it. Posting the observed behaviour and the minimal change that worked here in case it’s useful or points at the right fix.

---

_[View the full topic](https://meta.discourse.org/t/patreon-v2-sync-reports-n-pledges-0-users-synced-member-email-read-from-wrong-resource-in-extract/411874)._
