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.
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):
- Prefixo de tabela varia por site (
wpah_/wpbk_/wpne_confirmados ao vivo) —$wpdb->prefixresolve isto dentro do PHP; não faz sentido rastrear isto em TS por site. - Segredos cifrados —
fluentMailGetSettings()já decifra/mascarara consistentemente; não vale a pena reimplementar AES-256-CTR + LOGGED_IN_KEY/SALT em TypeScript. - Reenvio tem de passar pelo mecanismo real (
Logger::resendEmailFromLog()) — conversão de anexos, cabeçalhos,resent_count, histórico emextra— 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_configuredpor connection (fluentsmtp_list_connections). fluentsmtp_resend_emailexigeconfirm:true(zodz.literal(true), rejeitado no schema antes de chegar ao handler — testado ao vivo comconfirm:false, erro-32602antes de qualquer SSH).recipientsemfluentsmtp_resend_emailpermite 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_statusemdescomplicar: 2 connections, default=SMTP directo,fallback_connection_exists: false(órfão, confirmado por leitura directa da option); emstarter: 1 connection,secrets_encrypted_at_rest: false(confirmado,use_encryptausente nesse site); ememanuelalmeida: 1 connection, fallback presente mas vazio.fluentsmtp_list_connectionsemdescomplicareemanuelalmeida: segredos correctamente mascarados (secret_configured: falseno Elastic Email sem API key,trueno SMTP directo com password cifrada preenchida) — nunca o valor devolvido.fluentsmtp_list_email_logsemdescomplicarcomstatus:failed,page:2,per_page:3:total: 19(confirmado porSELECT COUNT(*) ... WHERE status='failed'directo), ids correctos para a página 2 depois da correcção do gotcha de paginação.fluentsmtp_get_email_logpara o log#50: destinatário, assunto e erro do provider ("SMTP Error: Could not authenticate.") confirmados coincidentes com a linha da tabela.fluentsmtp_get_statsemdescomplicar(01-19 ago 2026):lifetime.sent=58,lifetime.failed=19— confirmado porSELECT COUNT(*) GROUP BY statusdirecto; ememanuelalmeida:lifetime.sent=20,lifetime.failed=0.fluentsmtp_resend_email(escrita real, única):confirm:falserejeitado no schema antes de qualquer SSH (-32602 Invalid literal value, expected true).confirm:trueno log#74(descomplicar, falhado em 2026-08-14) comrecipients:["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 (extracom o registo do reenvio:to=it@descomplicar.pt,sent=true,ms=207.2).
Skills relacionadas
mcp-fluent-crm— mesmo mecanismowp evalsobre 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ãositecomo parâmetro em cada tool.