Throughout this book, we’ve built the components for Etsy automation. In this chapter, we examine how these tools come together in real production scenarios. Each case study represents a common pattern in digital product selling.
Sarah runs a shop selling digital wall art printables. She has 200+ designs in multiple sizes (5x7, 8x10, 11x14, 16x20) and needs to:
# case_studies/printable_art/size_manager.py
"""Automated size variant management for printable art."""
from typing import Dict, List, Tuple
from dataclasses import dataclass
from etsy_client.resilient_client import ResilientEtsyClient
from etsy_client.listings import ListingOperations
from etsy_client.inventory import InventoryOperations
@dataclass
class PrintSize:
"""Print size configuration."""
name: str
width: int # inches
height: int # inches
price_multiplier: float
@property
def aspect_ratio(self) -> float:
return self.width / self.height
class PrintableSizeManager:
"""
Manage print size variants for digital art.
"""
STANDARD_SIZES = [
PrintSize("5x7", 5, 7, 0.6),
PrintSize("8x10", 8, 10, 0.8),
PrintSize("11x14", 11, 14, 1.0), # Base price
PrintSize("16x20", 16, 20, 1.3),
PrintSize("18x24", 18, 24, 1.5),
PrintSize("24x36", 24, 36, 2.0),
]
def __init__(
self,
client: ResilientEtsyClient,
shop_id: int,
base_price: float = 5.00
):
self.client = client
self.shop_id = shop_id
self.base_price = base_price
self.listing_ops = ListingOperations(client)
self.inventory_ops = InventoryOperations(client)
def get_size_prices(self) -> Dict[str, float]:
"""Calculate prices for all sizes."""
return {
size.name: round(self.base_price * size.price_multiplier, 2)
for size in self.STANDARD_SIZES
}
def create_size_inventory(
self,
listing_id: int,
sizes: List[str] = None
) -> Dict:
"""
Create inventory products for sizes.
Args:
listing_id: Target listing
sizes: Size names to include (all if None)
"""
if sizes is None:
sizes = [s.name for s in self.STANDARD_SIZES]
prices = self.get_size_prices()
products = []
for size_name in sizes:
if size_name in prices:
products.append({
'sku': f"PRINT-{listing_id}-{size_name.replace('x', '')}",
'property_values': [{
'property_name': 'Size',
'values': [size_name]
}],
'offerings': [{
'price': prices[size_name],
'quantity': 999,
'is_enabled': True
}]
})
return self.inventory_ops.update_inventory(
listing_id,
products=products
)
def update_all_prices(
self,
new_base_price: float
) -> List[Dict]:
"""
Update prices across all printable listings.
Args:
new_base_price: New base price (11x14)
"""
self.base_price = new_base_price
prices = self.get_size_prices()
results = []
# Get all active listings
listings = self.listing_ops.get_all_listings(
self.shop_id,
state="active"
)
for listing in listings:
if self._is_printable(listing):
try:
inventory = self.inventory_ops.get_inventory(
listing['listing_id']
)
# Update prices
updated_products = []
for product in inventory.get('products', []):
size = self._extract_size(product)
if size and size in prices:
product['offerings'][0]['price'] = prices[size]
updated_products.append(product)
if updated_products:
self.inventory_ops.update_inventory(
listing['listing_id'],
products=updated_products
)
results.append({
'listing_id': listing['listing_id'],
'status': 'updated'
})
except Exception as e:
results.append({
'listing_id': listing['listing_id'],
'status': 'error',
'error': str(e)
})
return results
def _is_printable(self, listing: Dict) -> bool:
"""Check if listing is a printable."""
tags = listing.get('tags', [])
return any(
'printable' in tag.lower() or 'digital' in tag.lower()
for tag in tags
)
def _extract_size(self, product: Dict) -> str:
"""Extract size from product properties."""
for prop in product.get('property_values', []):
if prop.get('property_name', '').lower() == 'size':
values = prop.get('values', [])
if values:
return values[0]
return None
# Usage script
def manage_printable_sizes():
"""Example usage for printable art shop."""
from config import Config
from etsy_client.users import get_my_shop_id
client = ResilientEtsyClient(
api_key=Config.ETSY_API_KEY,
access_token=Config.ETSY_ACCESS_TOKEN
)
shop_id = get_my_shop_id(client)
manager = PrintableSizeManager(client, shop_id, base_price=5.00)
# Show current pricing
print("Current Size Pricing:")
for size, price in manager.get_size_prices().items():
print(f" {size}: ${price:.2f}")
# Update all prices with new base
print("\nUpdating all listings to new base price of $6.00...")
results = manager.update_all_prices(6.00)
updated = sum(1 for r in results if r['status'] == 'updated')
print(f"Updated {updated} listings")
Mike sells digital planners with seasonal themes. His challenges:
# case_studies/planner/collection_manager.py
"""Collection and bundle management for digital planners."""
from typing import Dict, List, Optional
from datetime import datetime, timedelta
from dataclasses import dataclass
from enum import Enum
from etsy_client.resilient_client import ResilientEtsyClient
from etsy_client.listings import ListingOperations, ListingData
from etsy_client.digital import DigitalFileOperations
class PlannerFormat(Enum):
PDF = "pdf"
GOODNOTES = "goodnotes"
NOTABILITY = "notability"
BOTH = "both"
@dataclass
class PlannerProduct:
"""Digital planner product definition."""
name: str
format: PlannerFormat
base_price: float
files: Dict[str, str] # format -> file path
tags: List[str]
season: Optional[str] = None
year: Optional[int] = None
month: Optional[int] = None
class CollectionLauncher:
"""
Launch collections of related products.
"""
def __init__(self, client: ResilientEtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.listing_ops = ListingOperations(client)
self.digital_ops = DigitalFileOperations(client)
def create_seasonal_collection(
self,
products: List[PlannerProduct],
collection_name: str,
launch_date: datetime = None
) -> List[Dict]:
"""
Create all products in a seasonal collection.
Products are created as drafts if launch_date is future.
"""
results = []
is_draft = launch_date and launch_date > datetime.now()
for product in products:
try:
# Create listing
listing = self._create_planner_listing(
product,
collection_name,
state="draft" if is_draft else "active"
)
# Upload files
listing_id = listing['listing_id']
for format_name, file_path in product.files.items():
self.digital_ops.upload_file(
self.shop_id,
listing_id,
file_path,
f"{product.name} - {format_name.upper()}"
)
results.append({
'product': product.name,
'listing_id': listing_id,
'status': 'created'
})
except Exception as e:
results.append({
'product': product.name,
'status': 'error',
'error': str(e)
})
return results
def _create_planner_listing(
self,
product: PlannerProduct,
collection: str,
state: str
) -> Dict:
"""Create a single planner listing."""
# Build tags
tags = list(product.tags)
tags.extend([
'digital planner',
'instant download',
product.format.value
])
if product.season:
tags.append(product.season)
if product.year:
tags.append(str(product.year))
listing_data = ListingData(
title=f"{product.name} | {collection} Collection",
description=self._generate_description(product),
price=product.base_price,
quantity=999,
who_made="i_did",
when_made="2020_2024",
taxonomy_id=self._get_planner_taxonomy(),
is_digital=True,
tags=tags[:13], # Max 13 tags
state=state
)
return self.listing_ops.create_listing(self.shop_id, listing_data)
def _generate_description(self, product: PlannerProduct) -> str:
"""Generate listing description."""
formats = ", ".join(product.files.keys())
return f"""
{product.name}
✨ WHAT'S INCLUDED:
• Digital planner file(s) in {formats} format
• Instant download after purchase
• For personal use only
📱 COMPATIBLE WITH:
• GoodNotes, Notability, Noteshelf
• Any PDF reader
• Works on iPad, tablet, or computer
⬇️ HOW TO USE:
1. Purchase and download
2. Import to your favorite app
3. Start planning!
💝 Thank you for shopping small!
"""
def _get_planner_taxonomy(self) -> int:
"""Get taxonomy ID for planners."""
# Digital planners typically: Craft Supplies > Planning
return 1234 # Replace with actual taxonomy ID
class BundleManager:
"""
Create and manage product bundles.
"""
def __init__(self, client: ResilientEtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.listing_ops = ListingOperations(client)
self.digital_ops = DigitalFileOperations(client)
def create_bundle(
self,
bundle_name: str,
component_listings: List[int],
discount_percent: float = 20
) -> Dict:
"""
Create a bundle listing from existing products.
Args:
bundle_name: Name for the bundle
component_listings: Listing IDs to bundle
discount_percent: Discount off combined price
"""
# Get component details
components = []
total_price = 0
all_files = []
for listing_id in component_listings:
listing = self.listing_ops.get_listing(listing_id)
components.append(listing)
price = listing['price']['amount'] / listing['price']['divisor']
total_price += price
# Get files
files = self.digital_ops.get_listing_files(self.shop_id, listing_id)
all_files.extend(files.get('results', []))
# Calculate bundle price
bundle_price = total_price * (1 - discount_percent / 100)
# Build bundle description
component_list = "\n".join(
f"• {c['title']}" for c in components
)
description = f"""
🎁 BUNDLE & SAVE {discount_percent}%!
This bundle includes:
{component_list}
Individual value: ${total_price:.2f}
Bundle price: ${bundle_price:.2f}
You save: ${total_price - bundle_price:.2f}
All items delivered instantly as digital downloads.
"""
# Create bundle listing
listing_data = ListingData(
title=f"{bundle_name} | BUNDLE SAVE {discount_percent}%",
description=description,
price=round(bundle_price, 2),
quantity=999,
who_made="i_did",
when_made="2020_2024",
taxonomy_id=self._get_planner_taxonomy(),
is_digital=True,
tags=['bundle', 'digital planner', 'instant download'],
state="active"
)
bundle = self.listing_ops.create_listing(self.shop_id, listing_data)
# Note: You would need to upload the combined files to the bundle
return {
'bundle_listing_id': bundle['listing_id'],
'components': len(components),
'original_price': total_price,
'bundle_price': bundle_price,
'discount': discount_percent
}
def _get_planner_taxonomy(self) -> int:
return 1234 # Replace with actual
class DatedProductManager:
"""
Manage products with expiration dates.
"""
def __init__(self, client: ResilientEtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.listing_ops = ListingOperations(client)
def deactivate_expired(self, reference_date: datetime = None) -> List[Dict]:
"""
Deactivate dated products that have expired.
Args:
reference_date: Date to check against (default: today)
"""
if reference_date is None:
reference_date = datetime.now()
results = []
listings = self.listing_ops.get_all_listings(
self.shop_id,
state="active"
)
for listing in listings:
expiry = self._extract_expiry(listing)
if expiry and expiry < reference_date:
try:
self.listing_ops.update_listing(
listing['listing_id'],
state="inactive"
)
results.append({
'listing_id': listing['listing_id'],
'title': listing['title'],
'expired': expiry.strftime('%Y-%m'),
'status': 'deactivated'
})
except Exception as e:
results.append({
'listing_id': listing['listing_id'],
'status': 'error',
'error': str(e)
})
return results
def _extract_expiry(self, listing: Dict) -> Optional[datetime]:
"""
Extract expiry date from listing.
Looks for year/month in tags or title.
"""
title = listing.get('title', '').lower()
tags = [t.lower() for t in listing.get('tags', [])]
# Check for year in title
import re
year_match = re.search(r'20\d{2}', title)
if year_match:
year = int(year_match.group())
# Check for month
months = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4,
'may': 5, 'jun': 6, 'jul': 7, 'aug': 8,
'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12
}
for month_name, month_num in months.items():
if month_name in title:
# Expires at end of that month
if month_num == 12:
return datetime(year + 1, 1, 1)
return datetime(year, month_num + 1, 1)
# Year only - expires end of year
return datetime(year + 1, 1, 1)
return None
Lisa sells Canva and PowerPoint templates. She needs to:
# case_studies/templates/analytics.py
"""Advanced analytics for template shops."""
from typing import Dict, List, Tuple
from collections import defaultdict
from datetime import datetime, timedelta
import statistics
from etsy_client.resilient_client import ResilientEtsyClient
from etsy_client.analytics import ShopAnalytics, ListingMetrics
class TemplateAnalytics:
"""
Analytics specifically for template shops.
"""
CATEGORIES = [
'resume', 'presentation', 'social-media',
'business-card', 'invoice', 'proposal',
'instagram', 'pinterest', 'facebook'
]
def __init__(self, client: ResilientEtsyClient, shop_id: int):
self.client = client
self.shop_id = shop_id
self.analytics = ShopAnalytics(client, shop_id)
def performance_by_category(self) -> Dict[str, Dict]:
"""
Analyze performance broken down by template category.
"""
metrics = self.analytics.get_all_listing_metrics()
by_category = defaultdict(lambda: {
'listings': 0,
'views': 0,
'favorites': 0,
'sales': 0,
'revenue': 0,
'prices': []
})
for m in metrics:
category = self._categorize(m.title)
by_category[category]['listings'] += 1
by_category[category]['views'] += m.views
by_category[category]['favorites'] += m.favorites
by_category[category]['sales'] += m.sales
by_category[category]['revenue'] += m.revenue
by_category[category]['prices'].append(m.price)
# Calculate averages
results = {}
for cat, data in by_category.items():
results[cat] = {
'listings': data['listings'],
'total_views': data['views'],
'total_favorites': data['favorites'],
'total_sales': data['sales'],
'total_revenue': data['revenue'],
'avg_price': statistics.mean(data['prices']) if data['prices'] else 0,
'avg_views': data['views'] / data['listings'] if data['listings'] else 0,
'conversion_rate': data['sales'] / data['views'] if data['views'] else 0
}
return dict(sorted(
results.items(),
key=lambda x: x[1]['total_revenue'],
reverse=True
))
def find_similar_products(
self,
listing_id: int,
limit: int = 5
) -> List[Dict]:
"""
Find similar products to a given listing.
Uses tags and category matching.
"""
from etsy_client.listings import ListingOperations
listing_ops = ListingOperations(self.client)
# Get target listing
target = listing_ops.get_listing(listing_id)
target_tags = set(target.get('tags', []))
target_category = self._categorize(target['title'])
# Get all listings
all_listings = listing_ops.get_all_listings(self.shop_id, "active")
# Score similarity
scored = []
for listing in all_listings:
if listing['listing_id'] == listing_id:
continue
listing_tags = set(listing.get('tags', []))
listing_category = self._categorize(listing['title'])
# Score based on tag overlap and category match
tag_overlap = len(target_tags & listing_tags)
category_match = 1 if listing_category == target_category else 0
score = tag_overlap * 2 + category_match * 5
if score > 0:
scored.append({
'listing_id': listing['listing_id'],
'title': listing['title'],
'score': score,
'shared_tags': list(target_tags & listing_tags)
})
# Sort by score
scored.sort(key=lambda x: x['score'], reverse=True)
return scored[:limit]
def trending_categories(self, days: int = 7) -> List[Tuple[str, float]]:
"""
Find categories with increasing sales.
"""
# This would compare current period to previous
current = self.performance_by_category()
# Sort by recent views as proxy for trending
return sorted(
[(cat, data['avg_views']) for cat, data in current.items()],
key=lambda x: x[1],
reverse=True
)
def _categorize(self, title: str) -> str:
"""Categorize listing by title."""
title_lower = title.lower()
for category in self.CATEGORIES:
if category in title_lower:
return category
return 'other'
class SimilarProductRecommender:
"""
Add similar product recommendations to listings.
"""
def __init__(
self,
client: ResilientEtsyClient,
shop_id: int
):
self.client = client
self.shop_id = shop_id
self.analytics = TemplateAnalytics(client, shop_id)
def update_descriptions_with_similar(
self,
max_recommendations: int = 3
) -> List[Dict]:
"""
Update listing descriptions to include similar products.
"""
from etsy_client.listings import ListingOperations
listing_ops = ListingOperations(self.client)
results = []
listings = listing_ops.get_all_listings(self.shop_id, "active")
for listing in listings:
listing_id = listing['listing_id']
# Find similar
similar = self.analytics.find_similar_products(
listing_id,
max_recommendations
)
if not similar:
continue
# Build recommendation text
rec_text = "\n\n---\n🎨 YOU MIGHT ALSO LIKE:\n"
for s in similar:
rec_text += f"• {s['title']}\n"
# Update description
current_desc = listing.get('description', '')
# Remove old recommendations if present
if '🎨 YOU MIGHT ALSO LIKE:' in current_desc:
current_desc = current_desc.split('🎨 YOU MIGHT ALSO LIKE:')[0].strip()
new_desc = current_desc + rec_text
try:
listing_ops.update_listing(
listing_id,
description=new_desc
)
results.append({
'listing_id': listing_id,
'recommendations_added': len(similar)
})
except Exception as e:
results.append({
'listing_id': listing_id,
'error': str(e)
})
return results
All case studies benefit from batch processing:
def batch_process(items, operation, batch_size=10, delay=1.0):
"""Process items in batches with rate limiting."""
import time
results = []
for i in range(0, len(items), batch_size):
batch = items[i:i + batch_size]
for item in batch:
try:
result = operation(item)
results.append({'item': item, 'result': result})
except Exception as e:
results.append({'item': item, 'error': str(e)})
# Respect rate limits
if i + batch_size < len(items):
time.sleep(delay)
return results
def should_price_increase(listing_metrics: ListingMetrics) -> bool:
"""Determine if listing price should increase."""
return (
listing_metrics.conversion_rate > 0.03 and # High conversion
listing_metrics.views > 500 and # Enough data
listing_metrics.favorite_rate > 0.05 # High interest
)
def safe_operation(primary_fn, fallback_fn, *args, **kwargs):
"""Try primary operation, fall back on error."""
try:
return primary_fn(*args, **kwargs)
except Exception as e:
logging.warning(f"Primary failed: {e}, using fallback")
return fallback_fn(*args, **kwargs)
You’ve seen how automation works in practice. The final chapter provides an action plan to implement these systems in your own shop.
| ← Chapter 12: Building a Complete Automation System | Table of Contents | Chapter 14: Action Plan → |