Files
Emanuel Almeida 1fc8756767 docs: skill mcp-fluent-smtp — MCP FluentSMTP multi-site (descomplicar.pt, starter, emanuelalmeida.pt)
Estado geral, connections com segredos sempre mascarados, logs de email com
filtros/paginacao, detalhe de log, estatisticas de envio, reenvio controlado
(confirm:true). Documenta tambem o mapeamento de dados do plugin (unica
fonte, nao existe skill de conhecimento separada) e a decisao deliberada de
nao expor escrita de configuracao de connections.
2026-08-19 06:05:47 +01:00

13 KiB

name, description, layer
name description layer
mcp-fluent-smtp MCP dedicado multi-site (node stdio, ligação `fluent-smtp` em ~/.omp/agent/mcp.json) para FluentSMTP 2.3.1 (WPManageNinja) em descomplicar.pt, starter.descomplicar.pt e emanuelalmeida.pt — controla a entrega de email do site, tratado como superfície de alto risco. Estado geral, connections configuradas (segredos SEMPRE mascarados), logs de email com filtros/paginação, detalhe de log, estatísticas de envio, reenvio controlado de 1 email falhado (confirm:true obrigatório). NENHUMA escrita de configuração de connections. Usar quando "fluentsmtp", "fluent smtp", "email não enviado", "logs de email wordpress", "reenviar email falhado", "connection smtp wordpress", "fallback connection smtp", "estatísticas de envio de email", ou qualquer diagnóstico/consulta de entrega de email nestes 3 sites. wiki

/mcp-fluent-smtp — MCP dedicado FluentSMTP (multi-site)

Projecto em /media/ealmeida/Dados/Dev/mcp-fluent-smtp/ (TypeScript, SDK MCP oficial, stdio). Não existe skill de conhecimento separada sobre o FluentSMTP — esta skill é a ÚNICA documentação, incluindo o mapeamento de dados (schema/option) que noutros casos vive numa skill "irmã" (ver wp-fastest-cache↔mcp-wpfc ou fluent-crm↔mcp-fluent-crm para esse padrão).

FluentSMTP controla a entrega de email do site inteiro — tratado como superfície de alto risco. Por isso este MCP é deliberadamente maioritariamente READ, e a única escrita (reenvio de 1 email) exige confirm:true e nunca toca em configuração de connection.

Sites conhecidos

Chamar fluentsmtp_list_sites para a lista actual. Confirmados ao vivo 19-08-2026, FluentSMTP 2.3.1 active nos três:

Alias Domínio Prefixo de tabela Connections Fallback
descomplicar descomplicar.pt (produção) wpah_ 2 (Elastic Email + SMTP directo porta 25, default) configurado mas órfão — aponta para um ID que já não existe em connections
starter starter.descomplicar.pt (staging) wpbk_ 1 (SMTP) não configurado (chave ausente)
emanuelalmeida emanuelalmeida.pt wpne_ 1 (SMTP) presente mas vazio (não configurado)

Também aceita um path absoluto directamente (/home/ealmeida/<site>) para sites fora desta lista, sem exigir rebuild do MCP — mas nesse caso confirmar primeiro que o FluentSMTP está activo lá.

Nota: emanuelalmeida.pt tem também um mu-plugin próprio descomplicar-fluentsmtp-auto-retry (descoberto via wp plugin list, must-use) — fora do âmbito deste MCP, mas relevante para diagnóstico de falhas nesse site especificamente (pode já estar a reenviar automaticamente antes de este MCP ser chamado).

Mapeamento de dados (schema)

FluentSMTP não expõe WP-CLI custom commands nem API REST própria. Toda a leitura/escrita passa pela camada de serviço PHP do plugin, via wp eval (ver secção Arquitectura).

Config (wp_options.fluentmail-settings) — option nativa PHP array (não string JSON dentro de string; wp option get --format=json funciona correctamente sem dupla-codificação):

{
  connections: {
    "<connId hex32>": {
      title: string,
      provider_settings: {
        provider: "smtp"|"ses"|"mailgun"|"sendgrid"|"sendinblue"|"sparkpost"|"pepipost"|
                  "postmark"|"elasticmail"|"gmail"|"outlook"|"tosend"|"cloudflare",
        sender_name, sender_email, force_from_name, force_from_email,
        // campo secreto por provider (ver mapa completo abaixo) — sempre AES-256-CTR
        // cifrado (LOGGED_IN_KEY/SALT) + base64 quando use_encrypt="yes"
        password | secret_key | api_key | client_secret,
        host, port, auth, encryption, auto_tls, return_path,  // só provider=smtp
        key_store: "db",  // ou "config" se a credencial vier de wp-config.php (não visto ainda)
      }
    }, ...
  },
  mappings: { "<email>": "<connId>" },       // roteamento por remetente
  misc: {
    default_connection: "<connId>",
    fallback_connection: "<connId>",         // PODE ficar órfão após apagar a connection alvo
    log_emails: "yes"|"no",
    log_saved_interval_days: "<N>",          // retenção do log em dias
    disable_fluentcrm_logs: "yes"|"no",
    send_as_text: "yes"|"no",
    simulate_emails: "yes"|"no",             // modo simulação — nunca envia de verdade
  },
  use_encrypt: "yes"|"",                     // se "", segredos ficam em claro na option (visto em starter)
  test: "<blob cifrado usado como canário de decifração>",
}

Mapa provider→campo secreto (replicado de helpers.php, fluentMailGetSettings/SetSettings — manter sincronizado se o plugin actualizar): smtp→password, ses→secret_key, mailgun/sendgrid/sendinblue/sparkpost/pepipost/postmark/ elasticmail/tosend/cloudflare→api_key, gmail/outlook→client_secret.

Logs (wp<prefixo>_fsmpt_email_logs, prefixo FLUENT_MAIL_DB_PREFIX='fsmpt_') — tabela custom, prefixo de tabela WordPress varia por site (ver tabela acima):

Coluna Tipo Nota
id int PK
to / headers / attachments longtext PHP-serializado — precisa maybe_unserialize()
from, subject varchar(255)
body longtext HTML/texto completo do email
status varchar(20) pending|sent|failed
response text serializado — erro do provider quando failed
extra text serializado — provider, send_time_ms, histórico resends[] (cada um com at,to,by,sent,ms)
retries / resent_count int tentativas automáticas vs reenvios manuais
created_at / updated_at timestamp índice composto (created_at, status)

Arquitectura — wp eval sobre a camada de serviço oficial, nunca SQL/wp option directo

Três razões concretas para este plugin (documentadas em src/wp-bridge.ts):

  1. Prefixo de tabela varia por site (wpah_/wpbk_/wpne_ confirmados ao vivo) — $wpdb->prefix resolve isto dentro do PHP; não faz sentido rastrear isto em TS por site.
  2. Segredos cifrados — fluentMailGetSettings() já decifra/mascarara consistentemente; não vale a pena reimplementar AES-256-CTR + LOGGED_IN_KEY/SALT em TypeScript.
  3. Reenvio tem de passar pelo mecanismo real (Logger::resendEmailFromLog()) — conversão de anexos, cabeçalhos, resent_count, histórico em extra — nunca um UPDATE SQL a simular isto.

Corpo PHP + dados de entrada (JSON) viajam em base64 dentro de uma variável de ambiente remota, nunca interpolados no comando SSH — nenhum ficheiro é escrito/apagado no servidor, o que evitaria deliberadamente o GATE 5.1 de mutação de infra-estrutura. Ver src/wp-bridge.ts (runPhp()) e src/php.ts (os 6 corpos PHP, um por tool).

Gotcha de paginação — não replicar sem testar primeiro: Logger::get()/ QueryBuilderHandler::paginate() (biblioteca interna wpfluent) lêem a página/per_page de $_GET['page']/$_REQUEST['per_page'] — pensado para pedidos HTTP do admin, inexistentes num wp eval de CLI. Sem preencher esses dois superglobais antes de chamar Logger::get(), a paginação colapsa sempre para page=1/per_page=15 ignorando silenciosamente qualquer page/per_page passado nos dados — reproduzido ao vivo antes de corrigir (fluentsmtp_list_email_logs com page:2 devolvia sempre a página 1 até $_GET['page']/$_REQUEST['per_page'] serem preenchidos explicitamente no corpo PHP, isolado a este processo efémero). Os filtros where()/whereBetween() (status/pesquisa/intervalo de datas) não sofrem deste problema — sobrevivem à chamada de paginate() porque ficam no query builder antes dela ser invocada.

Tools (6)

Tool Read-only Uso
fluentsmtp_list_sites sim Aliases conhecidos, path WP, prefixo de tabela, nota de estado
fluentsmtp_get_status sim Versão, nº connections, default/fallback (com verificação de existência — fallback pode ficar órfão), definições de log/retenção. Nunca devolve segredos
fluentsmtp_list_connections sim Todas as connections — provider, remetente, secret_configured: true/false (NUNCA o valor, mesmo cifrado), qual é default/fallback, e-mails mapeados
fluentsmtp_list_email_logs sim Filtros status/search/date_from+date_to, paginação real (page/per_page)
fluentsmtp_get_email_log sim Detalhe completo de 1 log por id — corpo truncado a 5000 caracteres, cabeçalhos, resposta do provider, histórico de reenvios em extra
fluentsmtp_get_stats sim Totais vitalícios (sent/failed), totais do período por status, série diária/semanal/mensal do volume total
fluentsmtp_resend_email não Reenvia de verdade 1 email via Logger::resendEmailFromLog(). Exige confirm:true. Aceita recipients para redireccionar o envio para endereço(s) controlados em vez do(s) destinatário(s) original(is)

Todas as tools exigem site como primeiro parâmetro.

Decisão deliberada: sem escrita de configuração de connections

Nenhuma tool escreve fluentmail-settings (criar/editar/apagar connection, mudar default/fallback, activar/desactivar simulação). Razão: um erro nessa escrita — chave errada, provider mal configurado, default_connection a apontar para um ID inexistente (já aconteceu organicamente em produção, ver fallback_connection órfão em descomplicar) — parte o envio de email do site inteiro (resets de password, notificações, formulários, FluentCRM), sem forma fácil de detectar isso a partir de fora até um utilizador reportar "não recebi o email". O risco/benefício não compensa para um v1. Se for necessário no futuro: replicar o padrão guarda-corpo + confirm:true + leitura-antes-de-escrever de mcp-fluent-crm (fcrm_update_contact_status), nunca um update_option cego.

Segurança

  • Segredos de connection nunca devolvidos em nenhuma tool, mesmo cifrados — só o booleano secret_configured por connection (fluentsmtp_list_connections).
  • fluentsmtp_resend_email exige confirm:true (zod z.literal(true), rejeitado no schema antes de chegar ao handler — testado ao vivo com confirm:false, erro -32602 antes de qualquer SSH).
  • recipients em fluentsmtp_resend_email permite testar/reenviar sem depender do(s) destinatário(s) original(is) — usado nos testes deste MCP para nunca enviar a um terceiro externo sem necessidade.
  • Corpo do log truncado a 5000 caracteres em fluentsmtp_get_email_log — não é segredo, mas evita devolver megabytes de HTML/anexos codificados.

Verificação

Construído e testado ponta-a-ponta contra os 3 sites reais em 19-08-2026, via processo stdio real (não só chamadas SSH soltas):

  • fluentsmtp_get_status em descomplicar: 2 connections, default=SMTP directo, fallback_connection_exists: false (órfão, confirmado por leitura directa da option); em starter: 1 connection, secrets_encrypted_at_rest: false (confirmado, use_encrypt ausente nesse site); em emanuelalmeida: 1 connection, fallback presente mas vazio.
  • fluentsmtp_list_connections em descomplicar e emanuelalmeida: segredos correctamente mascarados (secret_configured: false no Elastic Email sem API key, true no SMTP directo com password cifrada preenchida) — nunca o valor devolvido.
  • fluentsmtp_list_email_logs em descomplicar com status:failed, page:2, per_page:3: total: 19 (confirmado por SELECT COUNT(*) ... WHERE status='failed' directo), ids correctos para a página 2 depois da correcção do gotcha de paginação.
  • fluentsmtp_get_email_log para o log #50: destinatário, assunto e erro do provider ("SMTP Error: Could not authenticate.") confirmados coincidentes com a linha da tabela.
  • fluentsmtp_get_stats em descomplicar (01-19 ago 2026): lifetime.sent=58, lifetime.failed=19 — confirmado por SELECT COUNT(*) GROUP BY status directo; em emanuelalmeida: lifetime.sent=20, lifetime.failed=0.
  • fluentsmtp_resend_email (escrita real, única): confirm:false rejeitado no schema antes de qualquer SSH (-32602 Invalid literal value, expected true). confirm:true no log #74 (descomplicar, falhado em 2026-08-14) com recipients:["it@descomplicar.pt"] — redireccionado do destinatário original para este endereço interno da própria empresa, nunca a um terceiro externo. Resultado: status: sent, resent_count: 1, confirmado por leitura directa da linha (extra com o registo do reenvio: to=it@descomplicar.pt, sent=true, ms=207.2).

Skills relacionadas

  • mcp-fluent-crm — mesmo mecanismo wp eval sobre camada de serviço oficial, para contactos e campanhas em descomplicar.pt; FluentSMTP é o transporte que o FluentCRM usa para enviar.
  • mcp-wpfc / mcp-wpmeteor — outros MCPs multi-site do mesmo bundle, padrão site como parâmetro em cada tool.