← Back to recipes

Identify content themes that resonate with supporters

communicationsintermediateproven

The problem

You send emails and post on social media regularly, but you're guessing what content works. One month you focus on impact stories, next month on urgent appeals, but you don't systematically track what supporters actually engage with. You're spending time creating content that might not resonate.

The solution

Analyse engagement patterns across your content to identify which themes, formats, and topics get the strongest response. Use email open rates, click rates, social media engagement, and donation links to quantify what works. Build a data-driven content strategy based on what your supporters have shown they care about, not what you assume they want.

What you get

A content performance analysis showing: (1) Which themes get highest engagement (impact stories, fundraising appeals, programme updates, etc.), (2) Optimal content mix based on historical performance, (3) Themes to do more of vs less of, (4) Best-performing examples to learn from, (5) Content calendar recommendations based on data.

Before you start

  • At least 6 months of content history (emails, social posts) with engagement metrics
  • Basic tagging of content themes (even retrospectively is fine)
  • Access to engagement data: email open/click rates, social engagement, donation conversions
  • Willingness to be honest about what isn't working

When to use this

  • Creating regular content (weekly emails or daily social posts) without clear performance data
  • Content team debates what supporters want - need data to inform the discussion
  • Engagement rates feel low but not sure which content to change
  • You have 6+ months of content with engagement metrics to analyse
  • Want to optimise content mix rather than guessing

When not to use this

  • Very small audience (< 500) where individual preferences dominate aggregate patterns
  • Posting infrequently (monthly or less) - not enough data points
  • Can't tag content themes consistently (need categorisation for analysis)
  • Audience is so diverse that aggregate patterns are meaningless
  • Already have sophisticated content analytics and know what works

Steps

  1. 1

    Gather historical content and engagement data

    Export the last 6-12 months of: email campaigns (subject, send date, theme), social posts (content, platform, post date), engagement metrics (opens, clicks, likes, shares, comments, donations). Download from Mailchimp, Facebook Insights, Twitter Analytics, etc. Combine into one spreadsheet.

  2. 2

    Tag content with themes and formats

    For each piece of content, add tags: Theme (impact story, urgent appeal, programme update, thank you, awareness-raising). Format (photo, video, text, infographic, personal story). Tone (urgent, celebratory, informational, emotional). Do this retrospectively if needed - review and categorise consistently.

  3. 3

    Normalise engagement metrics across channels

    Different channels have different scales: 30% email open rate is great, but 30% social engagement rate would be incredible. Create normalised scores: compare each post to the average for that channel. E.g., 'This email did 1.5x our average open rate'. Makes cross-channel comparison meaningful.

  4. 4

    Analyse performance by theme

    Group content by theme and calculate average engagement. Which themes consistently outperform? Which underperform? Look at: email open rates, click rates, social engagement, conversion to donation if trackable. Rank themes by performance.

  5. 5

    Identify best-performing examples

    Within each theme, which specific pieces did exceptionally well? Pull out top 3-5 examples. Analyse what made them work: was it the subject line, the image, the storytelling, the call to action? Document patterns to repeat.

  6. 6

    Look for optimal content mix

    You can't just do high-performing content 100% of the time (too repetitive). Look for balanced mix: e.g., 40% impact stories (high engagement), 30% programme updates (medium engagement but important), 20% appeals (lower engagement but drives donations), 10% thank yous (good for retention). Data-informed balance, not data-dictated.

  7. 7

    Test hypotheses about what works

    Based on analysis, form hypotheses: 'Personal stories with photos outperform programme updates', 'Emotional tone works better than informational', 'Video drives more engagement than text'. Plan A/B tests to validate. Some patterns might be correlation not causation.

  8. 8

    Create data-driven content recommendations

    Summarise for content team: (1) Themes to do more of (with performance data), (2) Themes to reduce or rethink (with examples of why they're not working), (3) Optimal content mix, (4) Best practices from top performers, (5) Hypotheses to test. Update quarterly as you gather more data.

Example code

Analyse content performance by theme

Analyse content performance across themes to identify what resonates with supporters.

import pandas as pd
import matplotlib.pyplot as plt

# Load content data
df = pd.read_csv('content_history.csv')

# Calculate engagement score (normalised by channel)
# Email: combine open and click rate
# Social: engagement rate (likes + shares + comments / reach)

def normalize_engagement(row):
    """Normalize engagement relative to channel average"""
    channel_avg = df[df['channel'] == row['channel']]['engagement_rate'].mean()
    return row['engagement_rate'] / channel_avg if channel_avg > 0 else 0

df['normalized_engagement'] = df.apply(normalize_engagement, axis=1)

# Analyse performance by theme
theme_performance = df.groupby('theme').agg({
    'normalized_engagement': ['mean', 'std', 'count'],
    'engagement_rate': 'mean'
}).round(3)

theme_performance.columns = ['avg_normalized_engagement', 'std', 'count', 'raw_engagement']
theme_performance = theme_performance.sort_values('avg_normalized_engagement', ascending=False)

print("Content Performance by Theme:")
print(theme_performance)

# Visualise
theme_performance['avg_normalized_engagement'].plot(
    kind='barh',
    figsize=(10, 6),
    color=['green' if x > 1.0 else 'orange' if x > 0.8 else 'red' for x in theme_performance['avg_normalized_engagement']]
)
plt.axvline(x=1.0, color='black', linestyle='--', label='Average (1.0)')
plt.title('Content Theme Performance (Normalized Engagement)')
plt.xlabel('Normalized Engagement (1.0 = average)')
plt.ylabel('Theme')
plt.legend()
plt.tight_layout()
plt.savefig('theme_performance.png')

# Find best examples for each theme
print("\nTop Performing Examples:")
for theme in theme_performance.head(3).index:
    theme_df = df[df['theme'] == theme]
    top_example = theme_df.nlargest(1, 'normalized_engagement')
    print(f"\n{theme}:")
    print(f"  Best: '{top_example['title'].values[0]}'")
    print(f"  Engagement: {top_example['normalized_engagement'].values[0]:.2f}x average")
    print(f"  Platform: {top_example['channel'].values[0]}")

# Recommend content mix based on performance and volume
current_mix = df['theme'].value_counts(normalize=True).round(3)
performance_weights = theme_performance['avg_normalized_engagement']

print("\nCurrent vs Recommended Content Mix:")
print(f"{'Theme':<25} {'Current %':<12} {'Perf Score':<12} {'Recommendation'}")
for theme in current_mix.index:
    current = current_mix[theme] * 100
    perf = performance_weights.get(theme, 0)
    if perf > 1.2:
        rec = "Increase"
    elif perf < 0.8:
        rec = "Reduce"
    else:
        rec = "Maintain"
    print(f"{theme:<25} {current:>6.1f}%      {perf:>6.2f}        {rec}")

Tools

Google Sheetsservice · free
Visit →
Pythonplatform · free · open source
Visit →
pandaslibrary · free · open source
Visit →

Resources

At a glance

Time to implement
days
Setup cost
free
Ongoing cost
free
Cost trend
stable
Organisation size
small, medium, large
Target audience
comms-marketing, fundraising, operations-manager

Free tools are sufficient. Email and social platforms provide engagement data for free. Time: 2-3 days initial analysis, then 2 hours quarterly to update insights.

Part of this pathway