! O Problema
Afiliados enfrentam três problemas críticos na distribuição de ofertas:
- ✗Distribuição manual não escala:
Postar links manualmente em grupos gera repetição, spam e ban. Tempo gasto vs conversão não compensa.
- ✗Marketplaces são fragmentados:
Shopee, Mercado Livre, Amazon têm APIs diferentes, formatos diferentes, regras de afiliado diferentes.
- ✗WhatsApp pune comportamento robótico:
Sem controle de cadência, dedupe e humanização: shadow ban, bloqueio, queda de engajamento.
🎯 Objetivo
Criar uma plataforma que pareça humana, poste o que converte, no lugar certo, na hora certa, sem spam.
🏗️ Arquitetura Multi-Tenant
A arquitetura segue princípios rígidos que permitem evolução sem breaking changes:
Marketplace nunca hardcoded
Shopee é o primeiro conector, mas ML, Amazon, AliExpress são plug-and-play.
Canal nunca hardcoded
WhatsApp é o primeiro, mas Telegram, Discord, Instagram seguem a mesma interface.
Automação gera Post, não envia
Separação de concerns: Automação decide O QUE, Dispatcher decide COMO.
Dedupe obrigatório
Nenhum envio acontece sem passar pelo dedupe. Zero spam garantido.
Modelo de Dados
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Tenants │ │ Marketplaces │ │ Channels │
│ (multi-tenant) │ │ (Shopee, ML...) │ │ (WhatsApp, TG) │
└────────┬────────┘ └────────┬─────────┘ └────────┬────────┘
│ │ │
│ 1:N │ 1:N │ 1:N
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ tenant_marketplace_connections │
│ tenant_channel_connections │
│ (credenciais, affiliate_tag, config, status) │
└────────┬────────────────────────────────────────────────────────────┘
│
│ 1:N
▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Automations │─────▶│ AutomationRules │ │AutomationTargets│
│ (search/ranking)│ │ (keywords, price)│ │ (groups, limits)│
└────────┬────────┘ └──────────────────┘ └────────┬────────┘
│ │
│ 1:N │
▼ │
┌─────────────────┐ ┌──────────────────┐ │
│ Offers │─────▶│ Posts │◀──────────────┘
│ (normalized) │ │ (content ready) │
└─────────────────┘ └────────┬─────────┘
│
│ 1:N
▼
┌──────────────────┐
│ PostDispatches │
│ (sent/failed/...)│
└──────────────────┘Papéis e Permissões
SuperAdmin (Plataforma)
- • Cria tenants
- • Define quais marketplaces existem
- • Controla feature flags
- • Observa saúde global
Tenant Admin (Cliente)
- • Conecta canais (WhatsApp, Telegram)
- • Conecta afiliados (Shopee, ML)
- • Cria automações
- • Vê histórico e métricas
🔌 Provider Pattern
Cada marketplace implementa uma interface comum. O provider não sabe o que é tenant — tudo vem via tenant_connection. Isso permite adicionar novos marketplaces sem tocar no core.
class MarketplaceProvider:
"""Interface obrigatória para todo marketplace."""
def search(
self,
*,
rules: SearchRules,
limit: int,
tenant_connection: TenantMarketplaceConnection,
) -> list[OfferCandidate]:
"""Busca ofertas no marketplace."""
...
def build_affiliate_url(
self,
*,
product_url: str,
tenant_connection: TenantMarketplaceConnection,
) -> str:
"""Gera URL com tag de afiliado do tenant."""
...
def validate_connection(
self,
tenant_connection: TenantMarketplaceConnection,
) -> ValidationResult:
"""Valida credenciais (app_id, secret, affiliate_tag)."""
...Shopee Provider
class ShopeeProvider(MarketplaceProvider):
PROVIDER_KEY = "shopee"
GRAPHQL_ENDPOINT = "https://open-api.affiliate.shopee.com.br/graphql"
def search(self, *, rules, limit, tenant_connection):
credentials = self._get_credentials(tenant_connection)
# GraphQL query com filtros
query = self._build_search_query(
keyword=rules.query,
category=rules.category,
min_price=rules.min_price,
max_price=rules.max_price,
min_discount=rules.min_discount_percent,
)
# Assinatura HMAC para autenticação
signature = self._sign_request(query, credentials)
response = httpx.post(
self.GRAPHQL_ENDPOINT,
json={"query": query},
headers={"Authorization": f"SHA256 {signature}"},
timeout=20.0,
)
return self._parse_offers(response.json())
def _build_dedupe_key(self, item) -> str:
"""Identidade única: shopee:{shop_id}:{item_id}"""
return f"shopee:{item['shopId']}:{item['itemId']}"✓ Dedupe Key
O dedupe_key é a identidade real do produto: shopee:123:456. Nunca usar título para dedupe — variações de texto causariam spam.
⭐ Offer Scoring
Nem toda oferta vale a pena postar. O sistema de scoring ranqueia ofertas por potencial de conversão, penalizando produtos suspeitos.
# Score Components (0-10 each)
discount_score = min(discount_percent, 80) / 80 * 10
rating_score = (rating / 5) * 10
commission_score = min(commission_pct, 10) / 10 * 10
sales_score = min(sold_count, 1000) / 1000 * 10
# Weighted Sum
score = (discount_score * 0.40) +
(rating_score * 0.25) +
(commission_score * 0.15) +
(sales_score * 0.20) +
recency_bonus
# Recency Bonus
< 6h: +1.5
< 24h: +1.0
< 72h: +0.5
> 72h: +0.0
# Penalties (subtraídas do score)
fake_promo: discount > 70% AND rating < 4.1 → -2.0
no_reviews: rating is None or 0 → -1.0
too_cheap: price < R$5.00 → -0.5
high_commission: commission > 20% → -1.0Por que esses pesos?
- 40%Desconto: Principal driver de clique. Ofertas com desconto alto convertem mais.
- 25%Rating: Confiança do produto. Rating baixo = devolução = comissão cancelada.
- 20%Vendas: Prova social. Produto muito vendido tem validação de mercado.
- 15%Comissão: Retorno para o afiliado. Mas comissão alta demais é red flag.
🔄 Deduplicação Anti-Spam
O sistema de dedupe opera em dois escopos: por target (grupo específico) e por channel (todos os grupos do canal). Isso previne spam tanto vertical quanto horizontal.
Target Scope (48h)
Mesmo produto não pode ser enviado para o mesmo grupo em 48h. Evita repetição percebida pelos membros.
Channel Scope (2-5min)
Gap mínimo entre qualquer envio para qualquer grupo. Evita burst que parece bot.
class DedupeService:
def is_blocked(
self,
*,
tenant_id: UUID,
channel_id: UUID,
dedupe_key: str, # ex: "shopee:123:456"
target_ref: UUID | None,
scope: DedupeScope,
) -> bool:
"""
Check if offer is blocked by cooldown.
Returns True if still in cooldown, False if can send.
"""
record = self.db.query(DedupeRecord).filter(
DedupeRecord.tenant_id == tenant_id,
DedupeRecord.dedupe_key == dedupe_key,
DedupeRecord.scope == scope,
DedupeRecord.cooldown_until > now, # Still cooling
).first()
return record is not None
def mark_sent(self, *, dedupe_key, scope, post_id):
"""Mark offer as sent, starting cooldown."""
cooldown = (
timedelta(hours=48) if scope == DedupeScope.target
else timedelta(minutes=5)
)
record = DedupeRecord(
dedupe_key=dedupe_key,
cooldown_until=now + cooldown,
last_post_id=post_id,
)
self.db.add(record)✓ Cleanup Automático
Records expirados são deletados periodicamente via cleanup_expired(). A tabela não cresce indefinidamente.
🛡️ Anti-Ban (WhatsApp)
WhatsApp detecta bots por padrões de envio. O sistema implementa múltiplas camadas de humanização para evitar banimento:
- 🖊️Typing Indicator:
Antes de enviar, simula "digitando..." por 2-5 segundos (Evolution API).
- 🎲Jitter Humano:
Delay aleatório entre mensagens. Nunca intervalos exatos.
- 🚦Rate Limit por Conta:
Semáforo Redis limita msgs/minuto por número. Múltiplas contas = mais throughput.
- ⚡Circuit Breaker:
Erro 429 ou 503 → pausa automática de 30min para aquela conta.
- 🌙Quiet Hours:
Configurável por target: não envia entre 23h-8h (ou custom).
⚙️ Sistema de Automações
Automações são o coração do sistema. Definem O QUE buscar, ONDE enviar, e QUANDO executar.
# Pipeline de Execução (workers/tasks.py)
1. tick_automations()
└─ Busca automations habilitadas no intervalo
2. Para cada automation:
├─ Valida janela de horário (start_time/end_time)
├─ Carrega conexões ativas do tenant (marketplace + channel)
│
├─ Busca ofertas via provider.search()
│ └─ Aplica rules (keywords, price, discount)
│
├─ Normaliza ofertas + gera dedupe_key
│
├─ Aplica scoring + ranking
│ └─ priority_mode: max_discount | min_price | score
│
└─ Para cada target:
├─ Check dedupe (target scope)
├─ Check rate limit (channel scope)
├─ Check quiet hours
│
├─ typing() → delay → send()
│
├─ Cria Post + PostDispatch
└─ Marca dedupeTipos de Automação
search
Busca ativa no marketplace. Executa a cada intervalo configurado.
feed
Usa ofertas já no banco (ingestão externa). Aplica ranking e distribui.
monitor
Monitora preço de produtos específicos. Alerta quando desconto atinge threshold.
AutomationTarget (Limites)
automation_targets:
- target_ref: "uuid-do-grupo"
max_posts_per_day: 10 # Máximo de posts/dia neste grupo
min_gap_minutes: 30 # Mínimo entre posts (além do dedupe)
quiet_hours_start: "23:00"
quiet_hours_end: "08:00"🛠️ Stack Técnico
Backend
Frontend
Integrações
Infra
📊 Resultados
Explore Outros Projetos
Veja outros sistemas que construí