The ListContacts API does not return contacts that have OPT_OUT in their TopicPreferences for a specific topic when using FilteredStatus: OPT_OUT combined with TopicFilter. It only returns contacts where UnsubscribeAll is true.
This contradicts the documented behavior in the SES List Management Guide, which shows FilteredStatus + TopicFilter as the intended way to query contacts by topic subscription status.
Relevant documentation:
- ListContacts API Reference
- ListContactsFilter schema (describes FilteredStatus and TopicFilter fields)
- TopicFilter schema (describes UseDefaultIfPreferenceUnavailable)
- SES List Management Guide (walkthrough example that shows FilteredStatus + TopicFilter used together to query contacts by topic subscription status)
The Developer Guide walkthrough explicitly demonstrates this usage pattern:
{
"ContactListName": "ExampleContactListName",
"Filter": {
"FilteredStatus": "OPT_IN",
"TopicFilter": {
"TopicName": "Cycling",
"UseDefaultIfPreferenceUnavailable": true
}
},
"PageSize": 50
}
Per that guide, this call should return contacts whose effective subscription status for the "Cycling" topic is OPT_IN. By the same logic, FilteredStatus: OPT_OUT with a TopicFilter should return contacts opted out of that topic — but it does not.
STEPS TO REPRODUCE:
Verify List and Topic Setup
We have a contact list named ExampleContactList with a topic topic_1.
Verify Contact Preferences
A contact exists with an explicit topic-level OPT_OUT preference. Verified via GetContact:
aws sesv2 get-contact --contact-list-name ExampleContactList --email-address "<REDACTED_EMAIL>" --region eu-central-1
Response:
{
"ContactListName": "ExampleContactList",
"EmailAddress": "<REDACTED_EMAIL>",
"TopicPreferences": [
{
"TopicName": "topic_1",
"SubscriptionStatus": "OPT_OUT"
}
],
"TopicDefaultPreferences": [
{
"TopicName": "topic_2",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_3",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_4",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_5",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_6",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_7",
"SubscriptionStatus": "OPT_IN"
},
{
"TopicName": "topic_8",
"SubscriptionStatus": "OPT_IN"
}
],
"UnsubscribeAll": false,
"CreatedTimestamp": "2026-03-25T15:44:37.022000+03:00",
"LastUpdatedTimestamp": "2026-03-27T16:47:56.368000+03:00"
}
Note: The contact has UnsubscribeAll: false and one explicit OPT_OUT entry in TopicPreferences for topic_1.
Query ListContacts
Querying ListContacts with FilteredStatus: OPT_OUT and TopicFilter for that topic returns 0 contacts:
aws sesv2 list-contacts --contact-list-name ExampleContactList --filter '{"FilteredStatus":"OPT_OUT","TopicFilter":{"TopicName":"topic_1","UseDefaultIfPreferenceUnavailable":false}}' --region eu-central-1
Response:
{
"Contacts": [],
"NextToken": "[NEXT_TOKEN]"
}
Note: The contact above is not returned despite having an explicit OPT_OUT for topic_1.
EXPECTED BEHAVIOR:
Per the ListContactsFilter documentation and the List Management Guide walkthrough, the contact with TopicPreferences: [{ TopicName: "topic_1", SubscriptionStatus: "OPT_OUT" }] should be returned.
OBSERVED BEHAVIOR:
FilteredStatus: OPT_OUT only matches contacts where UnsubscribeAll: true. Topic-level OPT_OUT entries in TopicPreferences are ignored, making the TopicFilter field effectively non-functional for finding opted-out contacts.
ADDITIONAL OBSERVATION:
Omitting FilteredStatus while providing TopicFilter returns an HTTP 400 error with the message "Invalid FilteredStatus " — meaning the Filter object requires FilteredStatus.
This creates a catch-22: the field is required, but its value does not consider topic-level preferences. There is no API-native way to list contacts opted out of a specific topic. Per the Contact object schema, contacts expose both UnsubscribeAll and TopicPreferences, but FilteredStatus appears to only evaluate UnsubscribeAll.
IMPACT:
We are currently migrating from another provider to SES and need to sync topic-level unsubscribes bidirectionally across ~50,000 contacts and 10 topics. Due to this issue, we cannot use the ListContacts filter to efficiently query per-topic OPT_OUT contacts. We are forced to implement a workaround: fetching the entire contact list via a full paginated scan on every sync run and filtering in our application code by inspecting TopicPreferences entries.