Metadata-Version: 2.5
Name: shade-plugins-email
Version: 0.1.1
Summary: Ler e-mail por IMAP e enviar por SMTP, com TLS obrigatório e envio que não duplica.
Author: ShadeOne
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: shade-sdk<0.23,>=0.22.20
Description-Content-Type: text/markdown

# shade-plugins-email

Lê e-mail por IMAP e envia por SMTP de dentro de um robô do [`shade-sdk`](https://pypi.org/project/shade-sdk/),
com TLS obrigatório, credencial só do cofre e envio que não duplica numa nova execução. Só a
biblioteca padrão do Python: nenhuma dependência além do `shade-sdk`.

No `shade.toml` do robô, o servidor do cliente vai em `[config]` e em `[hosts] permitidos`, e o
usuário e a senha em `[credenciais]`:

```toml
[config]
email_imap = "imap.empresa.com.br"
email_smtp = "smtp.empresa.com.br"

[hosts]
permitidos = ["imap.empresa.com.br", "smtp.empresa.com.br"]

[credenciais]
nomes = ["EMAIL_USUARIO", "EMAIL_SENHA"]

[plugins]
email = "0.1.1"
```

```python
import shade_plugins_email as email

for msg in email.buscar(nao_lidas=True, assunto="pedido"):
    email.baixar_anexos(item, msg["uid"])  # grava em anexos/<uid>/ e anexa ao item

with item.efetivar("avisar o cliente"):
    email.enviar(para="cliente@exemplo.com", assunto="Pedido recebido", corpo="...")
```

| Operação | O que faz |
| --- | --- |
| `buscar(caixa="INBOX", *, nao_lidas, de, assunto, desde, limite=50)` | As mensagens da caixa, da mais nova à mais antiga: `uid`, `message_id`, `de`, `para`, `cc`, `assunto`, `data`, `tamanho`, `texto`, `html`, `anexos` (nome, tipo, tamanho) e `corpo_omitido` (acima de 25 MiB vem só o cabeçalho). `de` e `assunto` são trechos, sem diferenciar maiúsculas. |
| `baixar_anexos(item, uid, *, caixa="INBOX", nomes=None, pasta="anexos")` | Grava os anexos da mensagem em `<pasta>/<uid>/` no diretório de trabalho e os anexa ao item (`item.anexar`). O nome do arquivo perde separador de pasta e caractere de controle. |
| `enviar(para, assunto, corpo, *, cc, cco, de, html, anexos, em_resposta_a)` | Envia uma mensagem. Só dentro de `item.efetivar()`; no ensaio não roda. `cco` vai só no envelope; `anexos` são arquivos do diretório de trabalho (até 20 MiB); `de` é o `EMAIL_USUARIO` se omitido. Devolve `message_id` e `recusados`. |

- **Servidor só do `shade.toml`:** nenhum argumento nem dado do item escolhe outro servidor; o
  host fora de `[hosts] permitidos` é recusado antes de conectar (`HostNaoPermitido`).
- **TLS obrigatório e verificado:** `tls="implicito"` (o padrão: IMAP 993, SMTP 465) ou
  `tls="starttls"` (143 e 587). O servidor que não oferece STARTTLS é recusado antes do login
  (`TlsObrigatorio`); `porta=` troca a porta.
- **No executor da plataforma:** o robô não tem rota direta para fora, e o plugin abre a conexão
  pelo proxy do sandbox (`CONNECT`), só para os servidores de `[config]` e só nas portas 993
  (IMAP), 465 e 587 (SMTP). Outra porta é recusada com `ForaDoEgresso` (no `enviar`, um
  `EnvioNaoSaiu`). No `shade run` a conexão é direta, e o `HTTPS_PROXY` da máquina não é usado.
- **Ler não muda a caixa:** a caixa abre só para leitura e nada é marcado como lido.
- **Envio que não duplica:** o `Message-ID` deriva da chave do item, do nome da efetivação e da
  ordem do envio no bloco, e é o mesmo em toda execução. Se o robô cai logo depois de enviar, a
  nova execução retém o item para conferência em vez de enviar de novo; e, se alguém mandar
  refazer, a mensagem sai com o mesmo `Message-ID`, que o cliente de e-mail do destinatário
  reconhece como a mesma mensagem.
- **Retentativa só antes do envio:** a conexão e o login são tentados de novo na falha de rede.
  Depois que a mensagem começou a ir, nunca: `EnvioIncerto`. O que certamente não saiu (login
  recusado, destinatários recusados, servidor sem STARTTLS) é `EnvioNaoSaiu`, um
  `http.NadaSaiu`: com `item.efetivar(nome, desfazer_se=(http.NadaSaiu,))` o item volta à fila.
- **Prazo:** `timeout=` (30 s) vale para a chamada inteira, retentativas incluídas.
- **Registro:** um evento por chamada (a operação, a chave do servidor em `[config]`, a duração
  e o status), sem corpo, assunto, destinatário nem credencial.
