
User Generated Content
- 60 installs
- 42 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Lets customers upload photos, ask and answer product questions, and share social proof to raise trust and conversion for new visitors.
About
Adds customer photo uploads, product Q&A, and social-proof surfaces to product pages. A developer uses it to increase trust and conversion with authentic shopper content.
- Customer photo uploads and product Q&A
- Social proof to lift conversion for new visitors
User Generated Content by the numbers
- 60 all-time installs (skills.sh)
- Ranked #1,212 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 6, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill user-generated-contentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 60 |
|---|---|
| repo stars | ★ 42 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Lets customers upload photos, ask and answer product questions, and share social proof to raise trust and conversion for new visitors.
Files
User-Generated Content
Overview
User-generated content (UGC) — customer photos, Q&A, and recent purchase signals — increases product page conversion by giving new visitors authentic proof that real customers use and love the product. Review apps like Judge.me and Yotpo include UGC photo collection, product Q&A, and social proof widgets out of the box. Dedicated UGC platforms like Okendo, Loox, and Bazaarvoice provide more advanced sourcing from Instagram and TikTok. Only build a custom UGC pipeline if your moderation logic, rights management, or database requirements exceed what these tools support.
When to Use This Skill
- When product pages need authentic customer lifestyle photos beyond studio shots
- When building a Q&A section so customers can answer each other's questions
- When displaying "X people bought this in the last 24 hours" urgency signals
- When sourcing Instagram UGC tagged with your brand hashtag for the product page gallery
- When a third-party UGC platform is too expensive for the current scale
Core Instructions
Step 1: Determine platform and choose the right UGC tool
| Platform | Recommended Tool | Why |
|---|---|---|
| Shopify | Loox | Focused on photo reviews — sends post-purchase emails requesting photo uploads, displays a visual gallery on the PDP |
| Shopify | Okendo | Full UGC suite: photo + video reviews, Q&A, attributes ratings (sizing, quality), Instagram UGC sourcing |
| Shopify | Judge.me | Includes photo reviews in the free plan; good starting point before moving to Loox or Okendo |
| WooCommerce | WooCommerce built-in reviews + WP Product Review | Handles text + star reviews; add a media upload plugin for photo submissions |
| WooCommerce | Yotpo for WooCommerce | Full UGC including photo reviews, Q&A, and social proof |
| BigCommerce | Okendo or Yotpo | Both available on the BigCommerce App Marketplace |
| Custom / Headless | Build UGC API with S3 + moderation | Required when UGC data needs to live in your own database or integrate with a custom recommendation engine |
---
Step 2: Platform-specific setup
---
Shopify
Option A: Loox (recommended for photo-first UGC)
1. Install Loox from the Shopify App Store 2. Configure post-purchase photo request:
- Go to Loox → Emails → Photo Request Email
- Set send delay to 7 days after fulfillment
- Offer a discount incentive (e.g., 10% off next order) for submitting a photo review — Loox shows this offer in the request email
3. Configure the photo gallery widget:
- Go to Loox → Widgets → Product Widget
- Enable the photo carousel on your product template
- Set minimum photo count to show (only show gallery once you have 3+ photos per product)
4. Moderation:
- Go to Loox → Reviews → Pending to review and approve submitted photos
- Enable Auto-publish for verified purchase photo reviews
Option B: Okendo (full UGC including Q&A and Instagram)
1. Install Okendo from the App Store 2. Configure review forms to collect custom attributes: sizing, quality, value — these appear as filtered search options in the review widget 3. Set up Q&A in Okendo → Features → Q&A:
- Enable customer-to-customer Q&A below reviews
- Configure email notifications to your team when a new question is submitted
- Set up an auto-email notification to the question author when staff answers
4. Connect Instagram in Okendo → Channels → Instagram:
- Connect your brand Instagram account
- Okendo imports posts tagged with your hashtag and lets you request rights from the poster
- Only publish to product pages after rights are granted
Social proof notification widget (Loox and Okendo): Both apps include a "recent purchases" pop-up widget. Enable it in Loox → Widgets → Social Proof or Okendo → Widgets → Purchase Activity. Configure to show on product and collection pages, not on checkout.
---
WooCommerce
Yotpo for WooCommerce:
1. Install Yotpo Reviews from the WordPress plugin directory 2. Yotpo handles photo review requests, moderation, and gallery display 3. Configure post-purchase photo request timing in Yotpo → Settings → Review Requests 4. Enable Q&A under Yotpo → Features → Q&A
WooCommerce native reviews + photo upload: 1. Enable product reviews in WooCommerce → Settings → Products → Reviews 2. Install WooCommerce Product Reviews Pro extension to add:
- Photo upload field in the review form
- Verified purchase gating
- Upvote/helpful vote on individual reviews
3. For Q&A separately, install WooCommerce Product Q&A from WooThemes or a compatible plugin
Sourcing Instagram UGC: Use Tagembed or Smash Balloon Instagram Feed (WordPress plugins) to embed tagged Instagram posts on product pages. These pull posts by hashtag or mentions and let you curate which appear on each product.
---
BigCommerce
Okendo for BigCommerce:
1. Go to Apps → Search "Okendo" and install 2. Configuration is the same as the Shopify version — photo reviews, Q&A, and Instagram sourcing work identically 3. Okendo connects to BigCommerce orders for verified purchase gating automatically
Yotpo for BigCommerce: 1. Install from the BigCommerce App Marketplace 2. Includes photo reviews, Q&A, and SMS review requests 3. Yotpo's visual UGC tab imports Instagram and TikTok tagged content for use in the product gallery
---
Custom / Headless
For headless storefronts needing UGC in your own database:
// lib/ugc.ts
// Generate a presigned S3 URL for direct browser-to-S3 photo uploads
// Never route file uploads through your API server
export async function getPhotoUploadUrl(req: Request, res: Response) {
const { productId, fileName, contentType } = req.body;
if (!['image/jpeg', 'image/png', 'image/webp'].includes(contentType)) {
return res.status(400).json({ error: 'Only JPEG, PNG, and WebP accepted' });
}
const key = `ugc/pending/${productId}/${req.session.customerId}/${Date.now()}-${fileName}`;
const command = new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key, ContentType: contentType });
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300 });
await db.ugcPhotos.create({ data: { productId, customerId: req.session.customerId, s3Key: key, status: 'pending_upload' } });
res.json({ uploadUrl, key });
}
// S3 ObjectCreated trigger: run content moderation then resize
export async function processUGCPhoto(s3Key: string) {
const ugcRecord = await db.ugcPhotos.findFirst({ where: { s3Key } });
if (!ugcRecord) return;
// AWS Rekognition content moderation — auto-reject NSFW content
const moderationResult = await rekognition.detectModerationLabels({
Image: { S3Object: { Bucket: process.env.S3_BUCKET!, Name: s3Key } },
MinConfidence: 75,
});
if ((moderationResult.ModerationLabels ?? []).length > 0) {
await db.ugcPhotos.update({ where: { id: ugcRecord.id }, data: { status: 'rejected' } });
return;
}
// Resize to thumbnail (200px), medium (600px), full (1200px) WebP variants
const original = await s3.getObject({ Bucket: process.env.S3_BUCKET!, Key: s3Key });
const buffer = Buffer.from(await original.Body!.transformToByteArray());
for (const { suffix, width } of [{ suffix: 'thumbnail', width: 200 }, { suffix: 'medium', width: 600 }, { suffix: 'full', width: 1200 }]) {
const resized = await sharp(buffer).resize(width).webp({ quality: 80 }).toBuffer();
await s3.putObject({
Bucket: process.env.S3_BUCKET!, Body: resized, ContentType: 'image/webp',
Key: `ugc/approved/${ugcRecord.productId}/${ugcRecord.id}/${suffix}.webp`,
});
}
// Auto-approve verified purchase photos; queue others for manual review
const isVerifiedPurchase = await db.orderItems.findFirst({ where: { customerId: ugcRecord.customerId, productId: ugcRecord.productId } });
await db.ugcPhotos.update({ where: { id: ugcRecord.id }, data: {
status: isVerifiedPurchase ? 'approved' : 'pending_review',
verifiedPurchase: !!isVerifiedPurchase,
}});
}
// Product Q&A — submit and answer questions
export async function submitQuestion(req: Request, res: Response) {
const { productId, question, authorName } = req.body;
const q = await db.productQuestions.create({ data: { productId, question, authorName, authorEmail: req.session.customerEmail, status: 'pending' } });
await notifyProductTeam({ questionId: q.id, productId, question });
res.json({ questionId: q.id });
}
// Social proof feed: recent purchases cached for 5 minutes
export async function buildRecentPurchaseFeed() {
const recentOrders = await db.orders.findMany({
where: { createdAt: { gte: new Date(Date.now() - 86400000) }, status: 'completed' },
include: { lineItems: { include: { product: true } }, customer: true },
orderBy: { createdAt: 'desc' },
take: 100,
});
const feed = recentOrders.flatMap(order =>
order.lineItems.slice(0, 1).map(item => ({
productId: item.productId,
productName: item.product.name,
buyerFirstName: order.customer.firstName,
buyerLocation: order.customer.city ?? 'somewhere',
purchasedAt: order.createdAt.toISOString(),
}))
);
await redis.setex('social_proof_feed', 300, JSON.stringify(feed));
}---
Step 3: Configure rights management for Instagram UGC
Before displaying any customer social media content on your store or in paid ads:
1. Request rights explicitly — Okendo and Yotpo have built-in rights request flows that DM the Instagram user asking for permission 2. Never use Instagram content in paid advertising without written consent — this creates legal liability 3. Track rights status per photo — only display content where rights are confirmed; Okendo and Yotpo track this automatically 4. For organic sourcing (no dedicated app), comment on the post or send a DM with a rights request and keep a record of the response
---
Step 4: Set up Q&A moderation
Unanswered questions damage trust more than having no Q&A at all:
In Okendo and Yotpo:
- Configure a daily email digest to your team listing all unanswered questions
- Set a 48-hour SLA for staff answers — questions older than 7 days without answers should be escalated
Best practice: Enable customer-to-customer answers (other buyers can answer) — this reduces the staff burden and customers often provide more authentic answers than support agents.
---
Step 5: Measure UGC impact
| Metric | Benchmark | Where to Find |
|---|---|---|
| UGC photo submission rate | 2–5% of delivered orders | Loox / Okendo email analytics |
| Products with 3+ customer photos | Track % of top 50 products | App dashboard |
| Q&A answer rate | 90%+ within 7 days | Okendo / Yotpo Q&A report |
| Social proof widget click-through | 1–3% of product page visitors | Widget analytics in Loox/Okendo |
Best Practices
- Use Loox or Okendo before building custom — they handle S3 storage, content moderation, rights management, and Instagram import; building this from scratch takes 2–4 weeks
- Offer a small incentive for photo submissions — Loox's discount-for-photo feature increases submission rates by 3–5x compared to asking without an incentive
- Auto-approve verified purchase photos — these have lower risk and removing the manual review bottleneck dramatically increases UGC volume
- Show verified purchase badges on UGC photos — customers distinguish genuine-use photos from staged ones; the badge increases trust
- Always run AI content moderation before display — never approve photos without at least automated screening; AWS Rekognition or Google Vision SafeSearch catch NSFW content automatically
- Obtain explicit rights before using UGC in paid ads — using customer photos in advertising without written consent creates legal liability
Common Pitfalls
| Problem | Solution |
|---|---|
| Inappropriate content appears on product pages | Enable content moderation before approval; Loox and Okendo run automated screening automatically |
| Social proof widget shows stale or fake urgency | Use a 5-minute cache and only show genuine purchase signals — fabricating data creates reputational and legal risk |
| Q&A section has unanswered questions for weeks | Send a daily digest to the product team; questions older than 7 days without a staff answer damage trust more than no Q&A |
| UGC photos slow page LCP | Loox and Okendo serve via CDN with automatic resizing; for custom builds, always resize to 200px thumbnails and use lazy loading |
| Instagram posts used in paid ads without rights | Track rights approval status per post; Okendo handles this automatically — never publish to ads without confirmed consent |
Related Skills
- @product-reviews-ratings
- @personalization-engine
- @customer-segmentation
{
"context": "Tests whether the agent implements a correct S3-triggered image processing Lambda that uses AWS Rekognition for moderation, sharp for resizing, and correctly handles verified-purchase auto-approval logic.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Rekognition moderation used",
"max_score": 10,
"description": "Calls AWS Rekognition detectModerationLabels (via @aws-sdk/client-rekognition) to screen the uploaded image"
},
{
"name": "MinConfidence 75",
"max_score": 8,
"description": "Passes MinConfidence: 75 (not a different value) to detectModerationLabels"
},
{
"name": "Reject on any flagged label",
"max_score": 8,
"description": "Rejects the upload (sets status to 'rejected') whenever ModerationLabels contains one or more entries"
},
{
"name": "Rejection reason recorded",
"max_score": 6,
"description": "Stores the rejectionReason as the joined moderation label names (not just a boolean or generic message)"
},
{
"name": "Sharp for resizing",
"max_score": 8,
"description": "Uses the sharp library (not jimp, canvas, or another image library) to resize the uploaded image"
},
{
"name": "Three size variants",
"max_score": 10,
"description": "Produces exactly three size variants: thumbnail (200px wide), medium (600px wide), and full (1200px wide)"
},
{
"name": "WebP output quality 80",
"max_score": 8,
"description": "Converts each resized variant to WebP format with quality 80 (not another format or different quality value)"
},
{
"name": "Approved S3 key path",
"max_score": 8,
"description": "Stores resized variants under the path ugc/approved/{productId}/{ugcId}/{suffix}.webp"
},
{
"name": "Verified purchase auto-approve",
"max_score": 10,
"description": "Sets status to 'approved' automatically for verified purchasers (customers who ordered the same product)"
},
{
"name": "Pending review for non-verified",
"max_score": 10,
"description": "Sets status to 'pending_review' (not 'approved' or 'rejected') for uploads from customers without a verified purchase"
},
{
"name": "S3 ObjectCreated trigger",
"max_score": 8,
"description": "The processing function is triggered by (or explicitly handles) an S3 ObjectCreated event, not a direct API call or polling loop"
},
{
"name": "EXIF data stripped",
"max_score": 6,
"description": "Strips or omits EXIF metadata from the output image (e.g. uses sharp's default strip behaviour or explicitly calls .withMetadata(false))"
}
]
}
UGC Photo Processing Lambda
Problem/Feature Description
An e-commerce platform has been receiving customer photo submissions stored in an S3 bucket under a ugc/pending/ prefix. The team wants to automate processing those uploads: screening them for inappropriate content, resizing them into multiple display variants, and deciding whether they can be published immediately or need manual review first.
Currently photos are going directly to the approved gallery without any content screening, which has already caused one incident where an offensive image appeared on a product page. The team also wants to serve images in web-friendly sizes rather than the raw originals, and they want to fast-track photos from customers who can be verified to have purchased the product.
Your job is to write the Lambda (or equivalent async worker) that handles the S3:ObjectCreated event for the pending prefix. The function should screen, resize, and update the status of each uploaded photo appropriately.
Output Specification
Produce a TypeScript source file (e.g. process-ugc-photo.ts) that contains the full photo-processing logic. The file should be self-contained with all imports, configuration, and helper functions.
Include a brief processing-notes.md that explains: (1) which service you chose for content moderation and why, (2) what confidence threshold you used, (3) how you decided which photos are published automatically versus held for review, and (4) how you handled image resizing and format choices.
Input Files
The following file provides type definitions and stub helpers to use as a starting point.
=============== FILE: inputs/types.ts =============== export interface S3Event { Records: Array<{ s3: { bucket: { name: string }; object: { key: string; size: number }; }; }>; }
export interface UGCPhoto { id: string; productId: string; customerId: string; s3Key: string; status: string; verifiedPurchase: boolean; }
// Stub db — replace with real ORM calls export const db = { ugcPhotos: { findByS3Key: async (key: string): Promise<UGCPhoto> => ({ id: 'stub-id', productId: 'prod-1', customerId: 'cust-1', s3Key: key, status: 'pending_upload', verifiedPurchase: false }), update: async (id: string, data: Partial<UGCPhoto>): Promise<void> => {}, }, orderItems: { exists: async (query: { customerId: string; productId: string }): Promise<boolean> => false, }, };
{
"context": "Tests whether the agent implements a customer photo upload presign endpoint correctly, including accepted content types, S3 key structure, presigned URL expiry, and creation of a pending database record.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Presigned URL approach",
"max_score": 12,
"description": "Uses server-side presigned S3 URLs (e.g. getSignedUrl with PutObjectCommand) rather than routing the file through the API server"
},
{
"name": "Content type validation",
"max_score": 10,
"description": "Rejects content types other than 'image/jpeg', 'image/png', and 'image/webp' with an error response"
},
{
"name": "S3 key pending path",
"max_score": 10,
"description": "Constructs the S3 object key using the pattern ugc/pending/{productId}/{customerId}/{timestamp}-{fileName} (all four segments present)"
},
{
"name": "Presigned URL expiry 300s",
"max_score": 8,
"description": "Sets the presigned URL expiresIn to exactly 300 seconds"
},
{
"name": "Metadata on S3 object",
"max_score": 8,
"description": "Includes Metadata with productId and customerId in the PutObjectCommand"
},
{
"name": "Pending record created",
"max_score": 12,
"description": "Creates a database record for the UGC photo with status 'pending_upload' at the time of presign generation (not after upload)"
},
{
"name": "Client-side 10MB limit",
"max_score": 8,
"description": "Enforces or documents a 10 MB maximum file size on the client side (e.g. validation check, comment, or configuration)"
},
{
"name": "Correct AWS SDK imports",
"max_score": 8,
"description": "Uses @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner (not a deprecated or alternative S3 library)"
},
{
"name": "Response includes uploadUrl and key",
"max_score": 8,
"description": "The presign endpoint response JSON includes both the presigned URL (uploadUrl or equivalent) and the S3 key"
},
{
"name": "S3 bucket from env var",
"max_score": 8,
"description": "Uses process.env.S3_BUCKET (or equivalent environment variable) rather than hardcoding the bucket name"
},
{
"name": "AWS region from env var",
"max_score": 8,
"description": "Initialises the S3Client with region from process.env.AWS_REGION (or equivalent) rather than a hardcoded string"
}
]
}
Customer Photo Upload API
Problem/Feature Description
A growing outdoor apparel brand wants to let post-purchase customers submit lifestyle photos of themselves using their gear. The marketing team has found that authentic customer photos on product pages drive significantly higher conversion than studio shots alone. The engineering team previously attempted to accept photo uploads directly through the Express API server, but this caused memory spikes and occasional crashes when customers uploaded high-resolution images simultaneously.
Your task is to implement the backend API endpoint that initiates a customer photo upload. The endpoint must allow post-purchase customers to submit a photo for a given product without routing the file through the application server. The implementation must include proper file type validation and must record the upload intent in the database as soon as the upload slot is created.
Output Specification
Produce a TypeScript source file (e.g. ugc-upload.ts) containing the implementation of the presign endpoint. The file should be self-contained and include all necessary imports, the S3 client initialisation, validation logic, database record creation, and the response payload.
Also produce a short design-notes.md file explaining the key architectural decisions you made — in particular, why files are not routed through the API server and how the database record relates to the upload lifecycle.
Input Files
The following file is provided as a starting point. Extract it before beginning.
=============== FILE: inputs/schema.ts =============== // Existing DB schema types (read-only reference) export interface UGCPhoto { id: string; productId: string; customerId: string | null; s3Key: string; status: 'pending_upload' | 'pending_review' | 'approved' | 'rejected'; rejectionReason?: string; verifiedPurchase: boolean; createdAt: Date; approvedAt: Date | null; }
// Stub db object — replace with your actual ORM calls export const db = { ugcPhotos: { create: async (data: Partial<UGCPhoto>): Promise<UGCPhoto> => ({ id: 'stub', ...data } as UGCPhoto), }, };
{
"context": "Tests whether the agent correctly implements the social proof feed (Redis caching, 24-hour order window) and the Q&A notification system (product team alerts, email to question author, upvote deduplication, daily unanswered digest).",
"type": "weighted_checklist",
"checklist": [
{
"name": "Redis setex with 300s TTL",
"max_score": 10,
"description": "Caches the social proof feed using redis.setex (or equivalent) with a TTL of exactly 300 seconds"
},
{
"name": "24-hour order window",
"max_score": 10,
"description": "Fetches orders from the last 24 hours only (using subHours(new Date(), 24) or equivalent) and status 'completed'"
},
{
"name": "No fabricated signals",
"max_score": 8,
"description": "Social proof data is derived entirely from actual order records — no static arrays, hardcoded names, or invented purchase entries"
},
{
"name": "Product team notified on question",
"max_score": 8,
"description": "Calls a notification function (e.g. notifyProductTeam) after a new question is submitted"
},
{
"name": "Author email notification on answer",
"max_score": 10,
"description": "Sends a transactional email to the question author when their question is answered (conditional on authorEmail existing)"
},
{
"name": "Email includes product URL",
"max_score": 6,
"description": "The answer notification email includes the product URL (e.g. ${STORE_URL}/products/${slug})"
},
{
"name": "Upvote deduplication",
"max_score": 10,
"description": "The upvote endpoint checks for an existing vote by the same voter (customerId or IP) and returns 409 if already voted"
},
{
"name": "Daily unanswered digest",
"max_score": 10,
"description": "Implements or documents a daily digest/notification to the product team listing unanswered questions (e.g. a scheduled job or cron)"
},
{
"name": "7-day threshold mentioned",
"max_score": 8,
"description": "The unanswered-question follow-up logic references a 7-day threshold (questions unanswered for more than 7 days are highlighted or escalated)"
},
{
"name": "Gallery verified-first ordering",
"max_score": 8,
"description": "The photo gallery query orders results by verifiedPurchase descending before createdAt descending"
},
{
"name": "Verified purchase badge returned",
"max_score": 6,
"description": "The gallery API response includes a verifiedPurchase field (boolean or badge indicator) for each photo"
},
{
"name": "CDN URLs for photo responses",
"max_score": 6,
"description": "Returns CDN-based URLs (using CDN_URL environment variable or equivalent) for thumbnailUrl and mediumUrl, not direct S3 URLs"
}
]
}
Social Proof Widget and Q&A Notification System
Problem/Feature Description
A DTC furniture brand is launching two product-page features: a live "recently purchased" social proof ticker and a customer Q&A section. The social proof ticker must show real recent purchases to build urgency, while the Q&A section needs to ensure customers get timely answers and can signal which questions are most helpful.
The engineering lead has flagged two operational concerns: the social proof feed must not hammer the database on every page view (the product pages receive thousands of visits per hour), and the Q&A section has historically gone stale — customers asked questions and never heard back, which damaged trust. A previous attempt at a social proof feature was pulled after the legal team flagged that some "recent purchase" entries were fabricated.
Your task is to implement: (1) the social proof feed builder and its read endpoint, (2) the Q&A submission, answer, and upvote endpoints, and (3) a mechanism to notify the product team about unanswered questions.
Output Specification
Produce the following TypeScript source files:
social-proof.ts— feed builder and GET endpointqa.ts— question submission, answer, and upvote endpointsqa-digest.ts— a scheduled job or function that sends a daily digest of unanswered questions to the product team
Also produce a ugc-api-notes.md explaining: how you prevent database overload on the social proof endpoint, how you ensure questions don't go unanswered indefinitely, and how duplicate upvotes are prevented.
Input Files
The following file provides database and cache stubs.
=============== FILE: inputs/stubs.ts =============== import { subHours } from 'date-fns';
export { subHours };
// Redis stub export const redis = { setex: async (key: string, ttl: number, value: string): Promise<void> => {}, get: async (key: string): Promise<string | null> => null, };
// DB stubs export const db = { orders: { findMany: async (query: any): Promise<any[]> => [], }, productQuestions: { create: async (data: any): Promise<any> => ({ id: 'q-stub', ...data }), update: async (id: string, data: any): Promise<void> => {}, findById: async (id: string, opts?: any): Promise<any> => ({ id, question: 'Is this available in blue?', authorEmail: 'customer@example.com', product: { name: 'Lounge Chair', slug: 'lounge-chair' }, }), increment: async (id: string, field: string, amount: number): Promise<void> => {}, findUnanswered: async (olderThanDays?: number): Promise<any[]> => [], }, questionVotes: { exists: async (query: any): Promise<boolean> => false, create: async (data: any): Promise<void> => {}, }, ugcPhotos: { findMany: async (query: any): Promise<any[]> => [], }, };
export async function notifyProductTeam(data: any): Promise<void> {} export async function sendTransactionalEmail(to: string, template: string, vars: any): Promise<void> {} export async function sendDigestEmail(recipients: string[], subject: string, body: any): Promise<void> {}
{
"name": "finsi/user-generated-content",
"version": "0.1.0",
"summary": "Customer photos, Q&A sections, and social proof widgets",
"skills": {
"user-generated-content": {
"path": "SKILL.md"
}
}
}