mailcore 0.1.0.alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTING.md +22 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/SECURITY.md +28 -0
- data/app/controllers/action_mailbox/ingresses/mailcore/inbound_emails_controller.rb +59 -0
- data/docs/cobertura.md +140 -0
- data/docs/integracao-api-interrupcao.json +27 -0
- data/docs/integracao-api.json +48 -0
- data/docs/rails.md +185 -0
- data/docs/ruby-3.3.13-http-padrao.json +22 -0
- data/docs/ruby-3.3.13.json +66 -0
- data/docs/ruby-3.4.12-http-padrao.json +22 -0
- data/docs/ruby-3.4.12.json +66 -0
- data/docs/ruby-4.0.9-http-padrao.json +22 -0
- data/docs/ruby-4.0.9.json +66 -0
- data/docs/sinatra.md +94 -0
- data/docs/verificacao.md +100 -0
- data/exemplos/enviar.rb +14 -0
- data/exemplos/sinatra.rb +65 -0
- data/lib/mailcore/action_mailer.rb +124 -0
- data/lib/mailcore/assinatura.rb +64 -0
- data/lib/mailcore/cliente.rb +27 -0
- data/lib/mailcore/configuracao.rb +30 -0
- data/lib/mailcore/engine.rb +71 -0
- data/lib/mailcore/erros.rb +25 -0
- data/lib/mailcore/limite_ingress.rb +51 -0
- data/lib/mailcore/railtie.rb +16 -0
- data/lib/mailcore/recursos/automacoes.rb +52 -0
- data/lib/mailcore/recursos/base.rb +29 -0
- data/lib/mailcore/recursos/caixas.rb +199 -0
- data/lib/mailcore/recursos/campanhas.rb +72 -0
- data/lib/mailcore/recursos/chaves_api.rb +23 -0
- data/lib/mailcore/recursos/cli.rb +24 -0
- data/lib/mailcore/recursos/contatos.rb +179 -0
- data/lib/mailcore/recursos/dominios.rb +35 -0
- data/lib/mailcore/recursos/emails.rb +54 -0
- data/lib/mailcore/recursos/eventos.rb +31 -0
- data/lib/mailcore/recursos/modelos.rb +106 -0
- data/lib/mailcore/recursos/recebimento.rb +54 -0
- data/lib/mailcore/recursos/webhooks.rb +57 -0
- data/lib/mailcore/resposta.rb +22 -0
- data/lib/mailcore/transporte.rb +284 -0
- data/lib/mailcore/versao.rb +5 -0
- data/lib/mailcore.rb +36 -0
- metadata +189 -0
data/docs/rails.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Rails: envio e recebimento
|
|
2
|
+
|
|
3
|
+
O gem `mailcore` integra o cliente HTTP com Action Mailer e Action Mailbox. Rails
|
|
4
|
+
e Mail são dependências opcionais: o cliente Ruby funciona sem esses componentes.
|
|
5
|
+
A combinação verificada é Ruby 3.4, Rails/Action Mailer/Action Mailbox `8.1.3.1`,
|
|
6
|
+
Mail `2.9.1` e JSON `>= 2.7, < 3`. Consulte o relatório geral do SDK para as outras
|
|
7
|
+
versões Ruby efetivamente exercitadas. Esta faixa de JSON preserva a integração
|
|
8
|
+
com o decodificador do Active Support dessa versão Rails.
|
|
9
|
+
|
|
10
|
+
## Action Mailer
|
|
11
|
+
|
|
12
|
+
Configure a chave em runtime. O bloco permite um `Proc`, resolvido ao fazer cada
|
|
13
|
+
pedido, para carregar a credencial pelo mecanismo de segredos da aplicação:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# config/initializers/mailcore.rb
|
|
17
|
+
Mailcore.configurar do |configuracao|
|
|
18
|
+
configuracao.chave_api = -> { ENV.fetch('MAILCORE_API_KEY') }
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Ative o método de entrega:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# config/environments/production.rb
|
|
26
|
+
config.action_mailer.delivery_method = :mailcore
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
O carregamento padrão do Rails registra esse método pelo Railtie. Se o cliente
|
|
30
|
+
Ruby foi carregado antes do framework, carregue explicitamente
|
|
31
|
+
`require 'mailcore/railtie'` antes de inicializar a aplicação.
|
|
32
|
+
|
|
33
|
+
Os mailers e templates continuam sendo os do Rails:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
class AvisoMailer < ApplicationMailer
|
|
37
|
+
default from: 'Equipe <avisos@seu-dominio.example>'
|
|
38
|
+
|
|
39
|
+
def confirmacao
|
|
40
|
+
headers['mailcore_opcoes'] = {
|
|
41
|
+
chave_idempotencia: "pedido-#{params.fetch(:pedido_id)}-confirmacao",
|
|
42
|
+
tags: { tipo: 'confirmacao' }
|
|
43
|
+
}
|
|
44
|
+
mail(to: params.fetch(:destinatario), subject: 'Pedido confirmado')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `deliver_now` para aguardar a aceitação HTTP ou `deliver_later` para entregar a
|
|
50
|
+
execução ao Active Job configurado pela aplicação. O gem não cria uma fila. A
|
|
51
|
+
aceitação da API não comprova entrega SMTP.
|
|
52
|
+
|
|
53
|
+
A conversão inclui remetente com nome, destinatários, cópias, cópias ocultas, um
|
|
54
|
+
endereço de resposta, texto, HTML, multipart, cabeçalhos personalizados e anexos.
|
|
55
|
+
Anexos mantêm os bytes em base64 estrito, o tipo MIME e o Content-ID quando inline.
|
|
56
|
+
Cabeçalhos estruturais e reservados pelo Mailcore são filtrados; os demais seguem
|
|
57
|
+
as validações da API.
|
|
58
|
+
|
|
59
|
+
`mailcore_opcoes` aceita `chave_idempotencia`, `tags` e `agendada_em`. Esta última
|
|
60
|
+
usa o formato ISO 8601 com fuso aceito pela API. Essas opções são metadados do
|
|
61
|
+
adaptador e não se tornam cabeçalhos do email. Uma chave estável identifica a mesma
|
|
62
|
+
intenção de negócio, inclusive quando a aplicação decide repetir um job. O gem não
|
|
63
|
+
repete um envio automaticamente após falhas ambíguas.
|
|
64
|
+
|
|
65
|
+
Para uma configuração específica, use `config.action_mailer.mailcore_settings` ou
|
|
66
|
+
`delivery_method_options` do Rails. São aceitas as opções do cliente
|
|
67
|
+
`chave_api`, `url_base`, `tempo_limite`, `tempo_conexao`, `certificado_ca` e
|
|
68
|
+
`limite_resposta_bytes`, além de `chave_idempotencia`. A opção `cliente` permite
|
|
69
|
+
fornecer uma instância isolada já configurada; ela prevalece sobre as demais opções
|
|
70
|
+
de conexão. Configure cada organização com seu próprio cliente, sem trocar a
|
|
71
|
+
configuração global durante pedidos concorrentes.
|
|
72
|
+
|
|
73
|
+
O identificador retornado pela API é colocado no `message_id` do objeto Mail
|
|
74
|
+
somente após sucesso. Erros do cliente são propagados para o mecanismo normal de
|
|
75
|
+
falhas do Rails, sem substituir o identificador anterior.
|
|
76
|
+
|
|
77
|
+
## Action Mailbox
|
|
78
|
+
|
|
79
|
+
A aplicação precisa ter Action Mailbox, Active Record, Active Storage e Active
|
|
80
|
+
Job configurados. Faça a instalação e as migrações próprias do Rails no ambiente
|
|
81
|
+
da aplicação. O gem não altera o banco nem escolhe armazenamento ou filas.
|
|
82
|
+
|
|
83
|
+
Carregue o ingress e habilite-o:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# config/application.rb, depois de carregar os componentes Rails
|
|
87
|
+
require 'mailcore/engine'
|
|
88
|
+
|
|
89
|
+
# config/environments/production.rb
|
|
90
|
+
config.action_mailbox.ingress = :mailcore
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Cadastre no Mailcore um webhook do evento `email.received` apontando para:
|
|
94
|
+
|
|
95
|
+
```text
|
|
96
|
+
https://sua-aplicacao.example/rails/action_mailbox/mailcore/inbound_emails
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Guarde o segredo de assinatura nas credenciais criptografadas do Rails:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
action_mailbox:
|
|
103
|
+
mailcore_segredo: segredo_fornecido_pelo_webhook
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Como alternativa, configure `Mailcore.configuracao.segredo_webhook` pelo bloco
|
|
107
|
+
`Mailcore.configurar`; o padrão consulta `MAILCORE_WEBHOOK_SECRET`. A credencial
|
|
108
|
+
Rails tem precedência. Configure também uma chave de API com permissão para ler
|
|
109
|
+
o original recebido. A chave da API e o segredo do webhook têm funções distintas.
|
|
110
|
+
|
|
111
|
+
O endpoint aceita POST e exige assinatura válida do corpo bruto. Para corpos
|
|
112
|
+
dentro do limite, sem o ingress habilitado responde `404`; assinatura inválida resulta em `401`; configuração do
|
|
113
|
+
segredo ausente resulta em `503`. Eventos assinados de outros tipos recebem `204`
|
|
114
|
+
sem buscar conteúdo. O evento de recebimento deve conter `data.email_id` e um
|
|
115
|
+
array não vazio de endereços SMTP em `data.to`; envelope inválido resulta em
|
|
116
|
+
`422`. Corpos acima de 256 KiB são recusados com `413`.
|
|
117
|
+
|
|
118
|
+
Um middleware atua antes dos parsers e da instrumentação do Rails, somente no
|
|
119
|
+
POST desse ingress. Ele lê no máximo 256 KiB mais um byte, inclusive quando a
|
|
120
|
+
requisição não informa Content-Length. Um corpo permitido é recolocado em um
|
|
121
|
+
StringIO com os mesmos bytes e tamanho correto; o controller verifica a assinatura
|
|
122
|
+
desse conteúdo sem normalizar ou reserializar o JSON.
|
|
123
|
+
|
|
124
|
+
Nessa rota, o corpo não é convertido automaticamente em parâmetros Rails antes
|
|
125
|
+
da autenticação. Assim, os metadados de email não entram no campo de parâmetros
|
|
126
|
+
da instrumentação, e o parser Rails não registra o corpo de um JSON malformado.
|
|
127
|
+
Filtros adicionais para `data`, `_json` e credenciais são aplicados somente ao
|
|
128
|
+
ambiente da requisição, preservando os filtros da aplicação. Outras rotas e
|
|
129
|
+
métodos seguem seu processamento habitual.
|
|
130
|
+
|
|
131
|
+
Após validar a assinatura, o ingress baixa o original pela rota autenticada do
|
|
132
|
+
cliente, `recebimento.baixar_original`. Ele não visita URLs fornecidas no webhook
|
|
133
|
+
e não reconstrói anexos a partir de metadados incompletos. A falha desse download
|
|
134
|
+
retorna `503`, preservando a possibilidade de entrega posterior do webhook.
|
|
135
|
+
Falhas internas do banco ou armazenamento seguem o tratamento de erros do Rails.
|
|
136
|
+
|
|
137
|
+
Os bytes MIME do original são preservados. O ingress acrescenta cabeçalhos
|
|
138
|
+
`X-Original-To` com os destinatários validados do envelope assinado. Isso permite
|
|
139
|
+
que o roteamento normal do Action Mailbox também encontre cópias ocultas ou
|
|
140
|
+
destinos de encaminhamento ausentes do cabeçalho `To`. Esses endereços fazem parte
|
|
141
|
+
do conteúdo armazenado pela aplicação e devem receber a mesma proteção dos emails.
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
class ApplicationMailbox < ActionMailbox::Base
|
|
145
|
+
routing /^suporte@/i => :suporte
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
O Action Mailbox armazena a mensagem e encaminha o processamento pelo Active Job.
|
|
150
|
+
A deduplicação nativa considera o par Message-ID e checksum do conteúdo. A
|
|
151
|
+
repetição do mesmo original com Message-ID e o mesmo envelope recebe `204` sem
|
|
152
|
+
criar uma nova mensagem. Quando o original não contém Message-ID, o Rails gera um
|
|
153
|
+
identificador que inclui o hostname; não há garantia de deduplicação entre réplicas
|
|
154
|
+
nesse caso. Isso não transforma o webhook em uma entrega exatamente uma vez: um
|
|
155
|
+
original ou envelope diferente pode gerar outro registro, e os efeitos do mailbox
|
|
156
|
+
da aplicação também precisam ser idempotentes. Os prazos de replay e retenção são
|
|
157
|
+
responsabilidade do Mailcore e da aplicação, respectivamente.
|
|
158
|
+
|
|
159
|
+
Configure armazenamento protegido, a retenção do Action Mailbox e filtros de logs
|
|
160
|
+
na aplicação. Não registre o corpo bruto, endereços de destinatários, credenciais
|
|
161
|
+
ou cabeçalhos de assinatura. Os adaptadores não registram esses valores.
|
|
162
|
+
|
|
163
|
+
## Verificação local
|
|
164
|
+
|
|
165
|
+
Os testes do adaptador usam Action Mailer e Mail reais para converter mensagens,
|
|
166
|
+
com um cliente sintético no ponto de chamada HTTP. Os testes do ingress executam
|
|
167
|
+
uma aplicação Rails em memória, com SQLite, Active Storage em pasta temporária e
|
|
168
|
+
Active Job de teste. Eles verificam a assinatura por um vetor HMAC independente,
|
|
169
|
+
persistência do original, destinatário oculto, replay, acesso recusado e falha de
|
|
170
|
+
consulta. Esses testes não abrem portas, chamam produção ou enviam SMTP.
|
|
171
|
+
|
|
172
|
+
Uma entrada virtual de 4 GiB, sem Content-Length, comprova que a aplicação Rails
|
|
173
|
+
solicita uma única leitura de 262.145 bytes antes de recusar o corpo, sem alocar
|
|
174
|
+
os 4 GiB. Outros casos verificam os bytes assinados com espaços e quebras de linha,
|
|
175
|
+
a ausência de metadados de email nos parâmetros instrumentados, os filtros locais
|
|
176
|
+
e a propagação de erros de I/O posteriores à leitura.
|
|
177
|
+
|
|
178
|
+
Os arquivos `testes/testes_action_mailer.rb` e
|
|
179
|
+
`testes/testes_action_mailbox.rb` são executados em processos separados, usando as
|
|
180
|
+
dependências do ambiente Docker de testes do projeto. A configuração de banco e
|
|
181
|
+
armazenamento desses testes é descartável e exclusiva do processo.
|
|
182
|
+
|
|
183
|
+
Contratos Rails utilizados:
|
|
184
|
+
[Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html) e
|
|
185
|
+
[Action Mailbox](https://guides.rubyonrails.org/action_mailbox_basics.html).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:36Z",
|
|
3
|
+
"ruby": "3.3.13",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:e9e5e257301b57271cfde1fcbe2c800bd16b153dd9deb8e5c18aa637e7658b26",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 42,
|
|
12
|
+
"assercoes": 280,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"perfil": "net_http_padrao_do_ruby",
|
|
19
|
+
"net_http": "0.4.1",
|
|
20
|
+
"total_testes": 42,
|
|
21
|
+
"total_assercoes": 280
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:29Z",
|
|
3
|
+
"ruby": "3.3.13",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:e9e5e257301b57271cfde1fcbe2c800bd16b153dd9deb8e5c18aa637e7658b26",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_action_mailbox.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 13,
|
|
12
|
+
"assercoes": 67,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"arquivo": "testes/testes_action_mailer.rb",
|
|
19
|
+
"aprovado": true,
|
|
20
|
+
"testes": 9,
|
|
21
|
+
"assercoes": 37,
|
|
22
|
+
"falhas": 0,
|
|
23
|
+
"erros": 0,
|
|
24
|
+
"ignorados": 0
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"arquivo": "testes/testes_core.rb",
|
|
28
|
+
"aprovado": true,
|
|
29
|
+
"testes": 5,
|
|
30
|
+
"assercoes": 66,
|
|
31
|
+
"falhas": 0,
|
|
32
|
+
"erros": 0,
|
|
33
|
+
"ignorados": 0
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"arquivo": "testes/testes_recursos.rb",
|
|
37
|
+
"aprovado": true,
|
|
38
|
+
"testes": 156,
|
|
39
|
+
"assercoes": 513,
|
|
40
|
+
"falhas": 0,
|
|
41
|
+
"erros": 0,
|
|
42
|
+
"ignorados": 0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"arquivo": "testes/testes_sinatra.rb",
|
|
46
|
+
"aprovado": true,
|
|
47
|
+
"testes": 11,
|
|
48
|
+
"assercoes": 250,
|
|
49
|
+
"falhas": 0,
|
|
50
|
+
"erros": 0,
|
|
51
|
+
"ignorados": 0
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
55
|
+
"aprovado": true,
|
|
56
|
+
"testes": 42,
|
|
57
|
+
"assercoes": 280,
|
|
58
|
+
"falhas": 0,
|
|
59
|
+
"erros": 0,
|
|
60
|
+
"ignorados": 0
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"perfil": "dependencias_lockfile",
|
|
64
|
+
"total_testes": 236,
|
|
65
|
+
"total_assercoes": 1213
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:46Z",
|
|
3
|
+
"ruby": "3.4.12",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:92daa531a314ef9bef6bd11fcf8fab79f76f3b3a26191785198f1749c5c40989",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 42,
|
|
12
|
+
"assercoes": 280,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"perfil": "net_http_padrao_do_ruby",
|
|
19
|
+
"net_http": "0.6.0",
|
|
20
|
+
"total_testes": 42,
|
|
21
|
+
"total_assercoes": 280
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:40Z",
|
|
3
|
+
"ruby": "3.4.12",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:92daa531a314ef9bef6bd11fcf8fab79f76f3b3a26191785198f1749c5c40989",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_action_mailbox.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 13,
|
|
12
|
+
"assercoes": 67,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"arquivo": "testes/testes_action_mailer.rb",
|
|
19
|
+
"aprovado": true,
|
|
20
|
+
"testes": 9,
|
|
21
|
+
"assercoes": 37,
|
|
22
|
+
"falhas": 0,
|
|
23
|
+
"erros": 0,
|
|
24
|
+
"ignorados": 0
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"arquivo": "testes/testes_core.rb",
|
|
28
|
+
"aprovado": true,
|
|
29
|
+
"testes": 5,
|
|
30
|
+
"assercoes": 66,
|
|
31
|
+
"falhas": 0,
|
|
32
|
+
"erros": 0,
|
|
33
|
+
"ignorados": 0
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"arquivo": "testes/testes_recursos.rb",
|
|
37
|
+
"aprovado": true,
|
|
38
|
+
"testes": 156,
|
|
39
|
+
"assercoes": 513,
|
|
40
|
+
"falhas": 0,
|
|
41
|
+
"erros": 0,
|
|
42
|
+
"ignorados": 0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"arquivo": "testes/testes_sinatra.rb",
|
|
46
|
+
"aprovado": true,
|
|
47
|
+
"testes": 11,
|
|
48
|
+
"assercoes": 250,
|
|
49
|
+
"falhas": 0,
|
|
50
|
+
"erros": 0,
|
|
51
|
+
"ignorados": 0
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
55
|
+
"aprovado": true,
|
|
56
|
+
"testes": 42,
|
|
57
|
+
"assercoes": 280,
|
|
58
|
+
"falhas": 0,
|
|
59
|
+
"erros": 0,
|
|
60
|
+
"ignorados": 0
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"perfil": "dependencias_lockfile",
|
|
64
|
+
"total_testes": 236,
|
|
65
|
+
"total_assercoes": 1213
|
|
66
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:58Z",
|
|
3
|
+
"ruby": "4.0.9",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:650e4570a7b60591ee3f9ca2d17509e686f798261702b1b92d7e82a086117ab1",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 42,
|
|
12
|
+
"assercoes": 286,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"perfil": "net_http_padrao_do_ruby",
|
|
19
|
+
"net_http": "0.9.1",
|
|
20
|
+
"total_testes": 42,
|
|
21
|
+
"total_assercoes": 286
|
|
22
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data_utc": "2026-11-14T14:01:50Z",
|
|
3
|
+
"ruby": "4.0.9",
|
|
4
|
+
"plataforma": "x86_64-linux",
|
|
5
|
+
"imagem": "sha256:650e4570a7b60591ee3f9ca2d17509e686f798261702b1b92d7e82a086117ab1",
|
|
6
|
+
"resultado": "aprovado",
|
|
7
|
+
"suites": [
|
|
8
|
+
{
|
|
9
|
+
"arquivo": "testes/testes_action_mailbox.rb",
|
|
10
|
+
"aprovado": true,
|
|
11
|
+
"testes": 13,
|
|
12
|
+
"assercoes": 67,
|
|
13
|
+
"falhas": 0,
|
|
14
|
+
"erros": 0,
|
|
15
|
+
"ignorados": 0
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"arquivo": "testes/testes_action_mailer.rb",
|
|
19
|
+
"aprovado": true,
|
|
20
|
+
"testes": 9,
|
|
21
|
+
"assercoes": 37,
|
|
22
|
+
"falhas": 0,
|
|
23
|
+
"erros": 0,
|
|
24
|
+
"ignorados": 0
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"arquivo": "testes/testes_core.rb",
|
|
28
|
+
"aprovado": true,
|
|
29
|
+
"testes": 5,
|
|
30
|
+
"assercoes": 66,
|
|
31
|
+
"falhas": 0,
|
|
32
|
+
"erros": 0,
|
|
33
|
+
"ignorados": 0
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"arquivo": "testes/testes_recursos.rb",
|
|
37
|
+
"aprovado": true,
|
|
38
|
+
"testes": 156,
|
|
39
|
+
"assercoes": 513,
|
|
40
|
+
"falhas": 0,
|
|
41
|
+
"erros": 0,
|
|
42
|
+
"ignorados": 0
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"arquivo": "testes/testes_sinatra.rb",
|
|
46
|
+
"aprovado": true,
|
|
47
|
+
"testes": 11,
|
|
48
|
+
"assercoes": 250,
|
|
49
|
+
"falhas": 0,
|
|
50
|
+
"erros": 0,
|
|
51
|
+
"ignorados": 0
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"arquivo": "testes/testes_transporte.rb",
|
|
55
|
+
"aprovado": true,
|
|
56
|
+
"testes": 42,
|
|
57
|
+
"assercoes": 280,
|
|
58
|
+
"falhas": 0,
|
|
59
|
+
"erros": 0,
|
|
60
|
+
"ignorados": 0
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"perfil": "dependencias_lockfile",
|
|
64
|
+
"total_testes": 236,
|
|
65
|
+
"total_assercoes": 1213
|
|
66
|
+
}
|
data/docs/sinatra.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Integração com Sinatra
|
|
2
|
+
|
|
3
|
+
O [exemplo Sinatra](../exemplos/sinatra.rb) usa o mesmo cliente `mailcore` da
|
|
4
|
+
integração Ruby. Sinatra é uma dependência da aplicação e do ambiente de testes;
|
|
5
|
+
não é uma dependência de runtime do gem Mailcore. A combinação verificada usa
|
|
6
|
+
Sinatra `4.2.1` e Rack `3.2.8`, compatível com o conjunto Rails `8.1.3.1` usado nos
|
|
7
|
+
testes do projeto.
|
|
8
|
+
|
|
9
|
+
O exemplo é uma aplicação modular `Sinatra::Base`. Ele oferece apenas
|
|
10
|
+
`POST /enviar`, que envia uma mensagem fixa para o endereço configurado no boot.
|
|
11
|
+
O corpo e a query da requisição não escolhem destinatários, conteúdo, chave da
|
|
12
|
+
API, URL da API ou cliente.
|
|
13
|
+
|
|
14
|
+
## Configurar na aplicação
|
|
15
|
+
|
|
16
|
+
Forneça estas variáveis pelo mecanismo de segredos do seu ambiente Docker:
|
|
17
|
+
|
|
18
|
+
| Variável | Uso |
|
|
19
|
+
| ----------------------- | ------------------------------------------------------------------- |
|
|
20
|
+
| `MAILCORE_API_KEY` | Chave usada pelo SDK para falar com a API |
|
|
21
|
+
| `TOKEN_APLICACAO` | Token separado que autoriza chamadas ao exemplo |
|
|
22
|
+
| `MAILCORE_REMETENTE` | Remetente fixo em domínio autorizado |
|
|
23
|
+
| `MAILCORE_DESTINATARIO` | Destinatário fixo |
|
|
24
|
+
| `MAILCORE_API_URL` | Opcional: URL base HTTPS da API; usa o padrão do SDK quando ausente |
|
|
25
|
+
|
|
26
|
+
O token precisa ser uma String de 32 a 256 caracteres em Base64URL, usando letras,
|
|
27
|
+
números, `_` e `-`, e deve ser diferente da chave da API. Gere-o com uma fonte
|
|
28
|
+
criptograficamente segura, por exemplo `SecureRandom.urlsafe_base64(32)`. O exemplo
|
|
29
|
+
recusa configuração incompleta ou ambígua no boot. Ele guarda um resumo SHA256 do
|
|
30
|
+
token e compara resumos de tamanho fixo com a operação de tempo constante do
|
|
31
|
+
OpenSSL.
|
|
32
|
+
|
|
33
|
+
Em um `config.ru` da aplicação, carregue o exemplo e construa uma instância:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
require_relative 'exemplos/sinatra'
|
|
37
|
+
|
|
38
|
+
run ExemploMailcoreSinatra.construir
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Execute esse arquivo pelo servidor Rack da sua aplicação, dentro do ambiente
|
|
42
|
+
Docker configurado. O exemplo não inicia um servidor ao ser carregado. Configure
|
|
43
|
+
HTTPS na entrada da aplicação e preserve a comunicação verificada por HTTPS entre
|
|
44
|
+
o SDK e a API. Os testes deste projeto não publicam portas.
|
|
45
|
+
|
|
46
|
+
A fábrica também aceita `ambiente:` e `cliente:` no boot. Isso permite fornecer um
|
|
47
|
+
Hash de configuração isolado e um cliente já configurado, inclusive para testes.
|
|
48
|
+
Esses argumentos não são lidos dos parâmetros HTTP. A configuração global do gem
|
|
49
|
+
e o carregamento do cliente Ruby continuam independentes do Sinatra.
|
|
50
|
+
|
|
51
|
+
## Chamar a rota
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
curl --request POST "$URL_APLICACAO/enviar" \
|
|
55
|
+
--header "Authorization: Bearer $TOKEN_APLICACAO" \
|
|
56
|
+
--header 'Idempotency-Key: pedido-123-confirmacao' \
|
|
57
|
+
--header 'Content-Type: application/json' \
|
|
58
|
+
--data '{}'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
O corpo precisa ser um objeto JSON válido em UTF-8, com até 4 KiB. Seus campos são
|
|
62
|
+
ignorados pelo envio fixo. A chave de idempotência é obrigatória, contém de 1 a 256
|
|
63
|
+
caracteres ASCII visíveis e não aceita vírgulas. Preserve a mesma chave para uma
|
|
64
|
+
repetição explícita da mesma intenção de negócio.
|
|
65
|
+
|
|
66
|
+
A resposta de aceitação é `202`, contendo apenas `id` e `estado`. A aceitação da
|
|
67
|
+
API não comprova entrega SMTP. Autenticação inválida retorna `401`; chave de
|
|
68
|
+
idempotência ou JSON inválido, `400`; conteúdo acima do limite, `413`; tipo de
|
|
69
|
+
conteúdo diferente de JSON, `415`. Rotas e métodos sem implementação recebem
|
|
70
|
+
`404` sem chamar o SDK.
|
|
71
|
+
|
|
72
|
+
Falhas da API preservam um status de erro HTTP aplicável, com mensagem genérica.
|
|
73
|
+
Falhas de comunicação, redirecionamentos recusados e respostas de aceitação
|
|
74
|
+
incompletas resultam em `502`. O exemplo não devolve mensagens privadas de erro,
|
|
75
|
+
cabeçalhos da API, chaves ou detalhes internos, e não faz repetição automática.
|
|
76
|
+
|
|
77
|
+
## Verificação
|
|
78
|
+
|
|
79
|
+
O arquivo `testes/testes_sinatra.rb`, no repositório de fontes, usa Sinatra e
|
|
80
|
+
`Rack::MockRequest` reais, com um cliente sintético injetado no boot. A verificação
|
|
81
|
+
local concluiu 11 testes e 250 asserções em Ruby 3.4, sem falhas. Ela exercita
|
|
82
|
+
roteamento, autorização, configuração, corpo malicioso, JSON/UTF-8, limites,
|
|
83
|
+
idempotência repassada, respostas sanitizadas e falhas sem repetição.
|
|
84
|
+
|
|
85
|
+
Os testes não abrem um listener, não chamam a API externa e não enviam SMTP. A
|
|
86
|
+
repetição com a mesma chave confirma que o exemplo a preserva; a deduplicação na
|
|
87
|
+
API real depende do contrato do servidor. A matriz geral pode ser executada pelo
|
|
88
|
+
verificador Docker do projeto, `sh scripts/verificar.sh`, sem instalar Ruby ou
|
|
89
|
+
gems no host.
|
|
90
|
+
|
|
91
|
+
Fontes dos componentes:
|
|
92
|
+
[documentação do Sinatra](https://sinatrarb.com/intro.html),
|
|
93
|
+
[Sinatra 4.2.1](https://rubygems.org/gems/sinatra/versions/4.2.1) e
|
|
94
|
+
[Rack](https://github.com/rack/rack).
|
data/docs/verificacao.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Verificação da gem Ruby
|
|
2
|
+
|
|
3
|
+
A verificação é local e utiliza dados sintéticos. Não publica pacotes, não faz
|
|
4
|
+
deploy e não envia SMTP. Os relatórios JSON registram resultados observados,
|
|
5
|
+
versões, imagens e alcance de cada execução.
|
|
6
|
+
|
|
7
|
+
Em 14/11/2026, a matriz principal passou em Ruby **3.3.13, 3.4.12 e 4.0.9**.
|
|
8
|
+
O ensaio da API real local também passou nos dez grupos descritos abaixo, com
|
|
9
|
+
23 migrações, TLS verificado e limpeza concluída. Os números e revisões completos
|
|
10
|
+
permanecem nos relatórios JSON correspondentes.
|
|
11
|
+
|
|
12
|
+
## Matriz e distribuição
|
|
13
|
+
|
|
14
|
+
Execute na raiz do repositório:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
sh scripts/verificar.sh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
O host precisa de shell POSIX e Docker Linux. Ruby, Bundler, Rails, Sinatra e
|
|
21
|
+
ferramentas de compilação ficam nas imagens. As bases Ruby 3.3, 3.4 e 4.0 estão
|
|
22
|
+
fixadas por digest, e as dependências de desenvolvimento pelo `Gemfile.lock`.
|
|
23
|
+
Não há instalação de ferramentas no host.
|
|
24
|
+
|
|
25
|
+
Além do lockfile, a suíte HTTPS é repetida com o `net-http` original de cada Ruby
|
|
26
|
+
(0.4.1, 0.6.0 e 0.9.1, respectivamente). Seus relatórios terminam em
|
|
27
|
+
`-http-padrao.json`. Isso exercita também o caminho de detecção de resposta
|
|
28
|
+
incompleta nas versões anteriores do transporte HTTP.
|
|
29
|
+
|
|
30
|
+
As suítes são executadas em processos separados, sem rede externa, com checkout
|
|
31
|
+
somente leitura e diretório temporário para Rails/SQLite/Active Storage. O
|
|
32
|
+
servidor HTTPS de teste escuta somente dentro do container e não publica portas.
|
|
33
|
+
Os relatórios `ruby-*.json` contêm as contagens por suíte; uma execução válida
|
|
34
|
+
precisa de `resultado: "aprovado"`, sem falhas, erros ou testes ignorados.
|
|
35
|
+
|
|
36
|
+
- Contrato: método, caminho, filtros e corpo das 151 operações, recursos filhos,
|
|
37
|
+
identificadores escapados e preservação de `nil`/`false`.
|
|
38
|
+
- Protocolo HTTPS real: certificado/hostname, CAs, redirecionamentos, HTTP de erro,
|
|
39
|
+
idempotência, desconexões sem retry, prazo total, limites, respostas truncadas,
|
|
40
|
+
bytes binários, consumo em fluxo e isolamento entre threads.
|
|
41
|
+
- Assinatura: vetor HMAC independente, UTF-8, rotação, alteração do corpo e janela
|
|
42
|
+
temporal de cinco minutos.
|
|
43
|
+
- Rails: Action Mailer/Mail reais e aplicação Action Mailbox com SQLite,
|
|
44
|
+
armazenamento temporário e Active Job de teste. A fronteira HTTP desses
|
|
45
|
+
adaptadores usa um cliente sintético, conforme [o guia Rails](rails.md).
|
|
46
|
+
- Sinatra: aplicação Rack real, autenticação, idempotência, limites de corpo,
|
|
47
|
+
separação de credenciais e tratamento sanitizado dos erros.
|
|
48
|
+
|
|
49
|
+
O mesmo comando constrói a gem em `pkg/`, instala o artefato localmente dentro de
|
|
50
|
+
containers Ruby mínimos e verifica que o cliente puro carrega sem Rails/Sinatra.
|
|
51
|
+
O arquivo SHA-256 acompanha a gem. Os pacotes contêm código/documentação próprios,
|
|
52
|
+
sem testes, credenciais, arquivos de ambiente ou dependências vendorizadas.
|
|
53
|
+
|
|
54
|
+
## API e PostgreSQL reais locais
|
|
55
|
+
|
|
56
|
+
O ensaio adicional é separado por precisar do checkout backend e de imagens e
|
|
57
|
+
cache de compilação já preparados:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
sh scripts/verificar_api.sh
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
São usados o checkout `mailcore` ao lado de `libs-mailcore`, a imagem
|
|
64
|
+
`mailcore/ruby-testes:3.4`, um runtime local da API, uma imagem Rust e o cache
|
|
65
|
+
público Cargo. O controlador compila a API com `--offline --locked` em Docker e
|
|
66
|
+
registra revisão, hash dos fontes e binário. O cache não inclui credenciais Cargo.
|
|
67
|
+
Consulte `python3 testes/integracao_api.py --help` dentro da imagem para informar
|
|
68
|
+
outros caminhos/imagens; `MAILCORE_BACKEND` e `MAILCORE_IMAGEM_CLIENTE` ajustam o
|
|
69
|
+
wrapper. Nenhum compilador deve ser instalado no host para esse ensaio.
|
|
70
|
+
|
|
71
|
+
O controlador usa o socket Docker para criar rede interna exclusiva, configurações
|
|
72
|
+
efêmeras em memória e instâncias novas da API e PostgreSQL. A API usa HTTPS e
|
|
73
|
+
PostgreSQL usa TLS `verify-full`; não são publicadas portas. Chaves de dados,
|
|
74
|
+
certificados, usuários e organização são sintéticos e exclusivos. Não são
|
|
75
|
+
iniciados worker, Postal ou SMTP.
|
|
76
|
+
|
|
77
|
+
Os cenários aprovados incluem chaves Full/Sending e negação por escopo; envio
|
|
78
|
+
composto com Cc/Cco/CID, replay e conflito de idempotência; lote atômico; domínios;
|
|
79
|
+
contatos, catálogos e exportação CSV por fluxo; modelos/pastas/versões/publicação
|
|
80
|
+
com controle de revisão; CRUD de marketing sem disparo; caixas/etiquetas/rascunhos;
|
|
81
|
+
recebimento MIME real pendente de processamento; concessões CLI e revogação.
|
|
82
|
+
O recebimento usa um callback privado sintético assinado, sem iniciar Postal.
|
|
83
|
+
|
|
84
|
+
O resultado e a limpeza ficam em [integracao-api.json](integracao-api.json).
|
|
85
|
+
`resultado: "aprovado"` e `limpeza_concluida: true` são necessários para considerar
|
|
86
|
+
o ensaio aprovado. Credenciais, corpos e mensagens de email não são gravados no
|
|
87
|
+
relatório. O controlador trata SIGINT/SIGTERM e remove apenas seus recursos.
|
|
88
|
+
|
|
89
|
+
A [interrupção por SIGTERM](integracao-api-interrupcao.json) também foi exercitada:
|
|
90
|
+
a saída de falha era esperada e a limpeza foi concluída, sem recursos rotulados
|
|
91
|
+
restantes. Esse ensaio não verifica SIGKILL nem desfaz intenções já aceitas.
|
|
92
|
+
|
|
93
|
+
## Limites das conclusões
|
|
94
|
+
|
|
95
|
+
Uma API real local comprova os contratos exercitados no binário/backend registrado,
|
|
96
|
+
sem simular entrega SMTP. Os testes Rails não comprovam a configuração da fila,
|
|
97
|
+
banco, armazenamento ou segredos de uma aplicação de produção. A matriz Docker
|
|
98
|
+
não garante todos os sistemas operacionais, versões Rails ou ambientes hospedados.
|
|
99
|
+
Não foi medido desempenho em produção nem equivalência de latência com outro
|
|
100
|
+
serviço. Uma publicação ou implantação futura exige validação própria.
|
data/exemplos/enviar.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mailcore'
|
|
4
|
+
|
|
5
|
+
cliente = Mailcore::Cliente.new(chave_api: ENV.fetch('MAILCORE_API_KEY'))
|
|
6
|
+
pedido = {
|
|
7
|
+
remetente: ENV.fetch('MAILCORE_REMETENTE'),
|
|
8
|
+
destinatarios: [ENV.fetch('MAILCORE_DESTINATARIO')],
|
|
9
|
+
assunto: 'Integração Ruby Mailcore',
|
|
10
|
+
texto: 'Sua integração Ruby está pronta.',
|
|
11
|
+
html: '<p>Sua integração Ruby está pronta.</p>'
|
|
12
|
+
}
|
|
13
|
+
resposta = cliente.emails.enviar(pedido, chave_idempotencia: ENV.fetch('CHAVE_IDEMPOTENCIA'))
|
|
14
|
+
puts resposta[:id]
|