Configuração
Como construir um cliente, todas as opções e o que ele recusa antes de um pedido ser enviado.
Opções
use OpenEmail\OpenEmail; $client = new OpenEmail(); OpenEmail::init(timeout: 10);OpenEmail::getClient()->me->ping(); $billing = new OpenEmail(apiKey: (string) getenv('OPENEMAIL_BILLING_API_KEY')); echo $client->mode, ' ', $billing->mode, PHP_EOL;| Ponto de entrada | O que obtém |
|---|---|
| new OpenEmail(...) | Um cliente construído a partir dos argumentos nomeados que passar. Tudo o que omitir é lido do ambiente: a chave de OPENEMAIL_API_KEY ou um token de OPENEMAIL_ACCESS_TOKEN quando não passa nenhuma credencial, e o URL base de OPENEMAIL_BASE_URL quando não passa nenhum. |
| OpenEmail::createClient(...) | O mesmo cliente que new OpenEmail(...), para código que prefira chamar uma fábrica. |
| OpenEmail::init(...) | Constrói um cliente, guarda-o como cliente partilhado e devolve-o. Aceita os mesmos argumentos nomeados. |
| OpenEmail::getClient() | O cliente partilhado, a partir de qualquer ponto do processo. Se for chamado antes de init, constrói um a partir do ambiente na primeira chamada. |
| OpenEmail::resetClient() | Descarta o cliente partilhado, para que o próximo getClient() construa um novo, que é o que um teste quer entre casos. |
Converter getenv() para string é propositado. Uma variável que não está definida torna-se uma chave vazia, que o cliente recusa com uma mensagem que indica a variável de que precisa, ao passo que null recorreria silenciosamente a OPENEMAIL_API_KEY.
use OpenEmail\Http\CurlHttpClient;use OpenEmail\OpenEmail; $client = new OpenEmail( apiKey: (string) getenv('OPENEMAIL_API_KEY'), baseUrl: 'https://api.openemail.uk', httpClient: new CurlHttpClient(), maxRetries: 2, timeout: 30, userAgent: 'billing-service/1.4', headers: ['X-Team' => 'billing'], disableUpdateNotice: true,);| Opção | Predefinição | Notas |
|---|---|---|
| apiKey: | OPENEMAIL_API_KEY | Tem de começar por oe_live_ ou oe_test_. Só é lido do ambiente quando não passa nem apiKey: nem accessToken:. |
| accessToken: | OPENEMAIL_ACCESS_TOKEN | Um token de acesso OAuth, ou um invocável que devolva um. Veja Tokens de acesso OAuth mais abaixo. Passe uma chave ou um token, nunca ambos. |
| baseUrl: | https://api.openemail.uk | Ou OPENEMAIL_BASE_URL. As barras finais são removidas, e https:// é acrescentado antes de um host simples, ou http:// antes de um host nesta máquina: localhost, um endereço 127.x.x.x ou ::1. Uma credencial nunca é enviada por http simples para outro host, e 0.0.0.0 ou [::] é recusado quando o cliente é construído, porque são endereços onde um servidor escuta, não endereços para onde enviar pedidos. |
| timeout: | 30 | Segundos por tentativa, não por chamada, cobrindo a ligação e a leitura da resposta inteira. 0 desativa-o. files->upload espera pelo menos 600 segundos, a menos que passe timeout: nessa chamada. |
| maxRetries: | 2 | Tentativas adicionais após a primeira, em chamadas que é seguro repetir. Define-se no cliente, não por chamada. 0 desativa as repetições. |
| httpClient: | CurlHttpClient | A camada HTTP: qualquer coisa que implemente OpenEmail\Http\HttpClient, como Psr18HttpClient à volta do Guzzle ou do Symfony HttpClient, ou um falso num teste. A página Clientes HTTP aborda cada um. |
| headers: | [] | Enviados em todos os pedidos. |
| userAgent: | openemail-php/<version> | Enviados em todos os pedidos. |
| disableUpdateNotice: | false | Ignora a verificação, feita uma vez por processo, de uma versão mais recente no Packagist. A verificação só é executada na linha de comandos quando a saída padrão é um terminal, e OPENEMAIL_DISABLE_UPDATE_NOTICE também a desativa. |
Variáveis de ambiente
| Variável | O que faz |
|---|---|
| OPENEMAIL_API_KEY | A chave que um cliente usa quando não passa nem apiKey: nem accessToken:. |
| OPENEMAIL_ACCESS_TOKEN | Um token de acesso OAuth, lido apenas quando não passa nenhuma das duas credenciais e OPENEMAIL_API_KEY não está definida, por isso uma chave no ambiente tem prioridade. |
| OPENEMAIL_BASE_URL | O URL base quando não passa nenhum. A um host simples como localhost:2222 é acrescentado o esquema. |
| OPENEMAIL_DISABLE_UPDATE_NOTICE | Qualquer valor não vazio desativa o aviso de atualização, para todos os clientes do processo. |
| HTTPS_PROXY e NO_PROXY, ou https_proxy e no_proxy | O proxy através do qual o cURL se liga, e os hosts que vão diretamente. Veja Proxies e TLS mais abaixo. |
Cada variável é lida primeiro com getenv() e depois a partir de $_SERVER e $_ENV, por isso um valor que o seu framework carregou de um ficheiro .env também conta. Uma variável definida mas vazia conta como não definida.
O que recusa antes de enviar
Estes casos lançam OpenEmail\Exception\InvalidArgumentException a partir da linha que continha o valor errado, em vez de surgirem como uma falha confusa no seu primeiro envio. A mensagem indica o que estava errado e o que passar em vez disso, e nunca repete uma credencial.
| Recusado | Porquê |
|---|---|
| Nenhuma credencial | Não foi passado nem apiKey: nem accessToken: e nenhuma das duas variáveis estava definida, por isso não há nada com que autenticar. Lançado quando o cliente é construído. |
| Uma chave e um token em simultâneo | Cada pedido leva uma só credencial, por isso o cliente não consegue saber a qual se referia. |
| Um cookie de sessão, um token de sessão ou uma chave de outro serviço | Só oe_live_ e oe_test_ autenticam aqui, e a API também o diz. A verificação é apenas de prefixo, pelo que uma chave revogada só é rejeitada quando o pedido é enviado, como AuthenticationException. |
| Um baseUrl: que não é um URL http ou https, ou que contém um nome de utilizador ou uma palavra-passe | Não é possível chegar a mais nada, e uma credencial pertence a apiKey: ou accessToken:, não ao URL. Lançado quando o cliente é construído. |
| Uma credencial por http simples para um host que não está nesta máquina | Lançado pela chamada, antes de qualquer envio. Use um URL base https. |
| Um timeout: negativo | Passe segundos, ou 0 para não ter timeout. Lançado quando o cliente é construído, ou pela chamada, no caso de um timeout passado a uma só chamada. |
| Um nome de cabeçalho que não é um token, ou uma quebra de linha ou outro carácter de controlo no valor de um cabeçalho | Verificado em headers:, userAgent: e idempotencyKey:, porque uma quebra de linha iniciaria um segundo cabeçalho. Os espaços, tabulações e quebras de linha à volta de um valor são removidos primeiro, como o fetch os remove, por isso uma chave lida de um ficheiro que termina numa quebra de linha continua a funcionar. |
| Um id vazio ou composto só por pontos em qualquer método | Lançado quando o método é chamado. Um segmento de caminho feito de pontos é removido por qualquer parser de URL, pelo que o pedido chegaria a um endpoint diferente. Um id que não seja UTF-8 válido também é recusado. |
| Conteúdo de anexo que não está em base64 | Uma string é sempre lida como base64, por isso bytes em bruto numa string seriam enviados como lixo. Codifique-os com OpenEmail::toBase64(), ou passe um SplFileInfo, um stream ou um stream PSR-7 e o cliente codifica-o. |
A classe estende a própria InvalidArgumentException do PHP, por isso o código que já a apanha continua a funcionar, e implementa OpenEmail\Exception\OpenEmailException, como todas as outras exceções que o pacote lança. Um valor do tipo errado, como um número onde vai um id em string, é um TypeError do próprio PHP, porque todos os métodos declaram os seus tipos.
Não existe uma opção testMode: nem vai existir. O esquema da chave faz parte da credencial e não é uma mera indicação, pelo que o modo é uma propriedade da chave. $client->mode lê o prefixo, live ou test, e não decide nada.
Um cliente, várias chaves
Construa o cliente uma vez e partilhe-o. Um cliente novo por pedido deita fora a sua ligação aberta sem qualquer ganho, e nenhum do estado que contém é específico de quem chama.
No caso que, de outro modo, obrigaria a um cliente por chave, como uma tarefa que envia em nome de vários espaços de trabalho, passe apiKey: na chamada. Substitui o cabeçalho Authorization para esse pedido e não deixa nada no cliente.
$message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your invoice', 'text' => 'Attached.',];$workspaceKey = (string) getenv('OPENEMAIL_API_KEY'); $client->emails->send($message); $client->emails->send($message, apiKey: $workspaceKey); $client->threads->list(folder: 'inbox', apiKey: $workspaceKey);$client->webhooks->list(apiKey: $workspaceKey);Todos os métodos fora de tempMail recebem-na como último argumento nomeado, depois dos filtros de uma lista, e os métodos de tempMail recebem antes inboxToken:. É verificada antes de o pedido ser enviado, pela mesma regra que o cliente usa, pelo que um erro de escrita lança um InvalidArgumentException sobre a apiKey passada nesta chamada em vez de um 401 sobre uma credencial que depois tem de ir procurar. Uma chamada repetida mantém a chave que lhe foi dada.
$client->mode descreve a chave com que o cliente foi CONSTRUÍDO e não acompanha uma substituição. Quando um cliente serve várias chaves, não há um modo único a indicar, por isso leia-o a partir da chave que passou. var_dump($client) mostra o modo e o URL base, nunca a chave, e todos os parâmetros que recebem uma credencial estão marcados com #[\SensitiveParameter], por isso um stack trace imprime um marcador no seu lugar.
Endpoints que nenhum método envolve
$client->raw é o transporte por onde passa cada método. $client->raw->request() chama um caminho que nenhum método envolve ainda, aplicando a credencial, o URL base, o timeout e a política de repetições do cliente, e devolve o corpo descodificado tal como um método faz.
$ping = $client->raw->request('/ping'); $label = $client->raw->request('/labels', method: 'POST', body: ['name' => 'Invoices']); var_dump($ping, $label);| Argumento nomeado | O que faz |
|---|---|
| method: | GET, a menos que indique outro: POST, PUT, PATCH ou DELETE. |
| query: | Um array de parâmetros de consulta. Os valores null e vazios são omitidos, uma lista é unida com vírgulas, e um DateTimeInterface é enviado como um instante ISO 8601 em UTC. |
| body: | Um array, enviado como JSON. |
| raw: e contentType: | Bytes a enviar tal como estão, como string, recurso de stream, SplFileInfo ou stream PSR-7, com application/octet-stream a menos que indique um tipo. |
| accept: e binary: | Um accept: diferente de JSON devolve o corpo como texto, e binary: true devolve-o como uma string de bytes. |
| idempotent: e idempotencyKey: | idempotent: true anexa uma Idempotency-Key, gerada a menos que passe a sua. |
| repeatable: | Se uma falha é repetida. Só um GET o é, a menos que passe repeatable: true. |
| anonymous: | true não envia credencial nenhuma. |
| apiKey:, inboxToken: e timeout: | As mesmas credenciais por chamada, e um timeout em segundos só para esta chamada. |
O caminho tem de começar por uma única /, e um caminho cujo URL final sairia da origem do URL base lança InvalidArgumentException antes de qualquer envio, por isso a credencial nunca chega a outro host.
Caixas de entrada descartáveis
OpenEmail::createTempMail() constrói um cliente para caixas descartáveis que não transporta nenhuma chave de API nem lê nenhuma do ambiente. Cria caixas de entrada de forma anónima, e cada leitura envia o token da caixa que create devolveu, ou o mais recente que extend devolveu, seja por chamada como inboxToken:, seja uma única vez como OpenEmail::createTempMail(inboxToken: ...).
use OpenEmail\OpenEmail; $tempMail = OpenEmail::createTempMail(); $inbox = $tempMail->create();$page = $tempMail->listMessages($inbox['id'], inboxToken: $inbox['token']); echo count($page->items), ' ', $page->expiresAt, PHP_EOL;OpenEmail::createTempMail() aceita baseUrl:, httpClient:, maxRetries:, timeout:, userAgent:, headers: e disableUpdateNotice: como qualquer cliente, e lê OPENEMAIL_BASE_URL quando não passa nenhum URL base.
Tokens de acesso OAuth
Uma aplicação que alguém ligou por OAuth, como uma ferramenta de linha de comandos ou um agente, tem um token de acesso em vez de uma chave de API. Passe-o como accessToken:, seja o próprio token, seja um invocável que o devolva, como uma closure ou um first-class callable. O invocável é executado uma vez por cada chamada, e as repetições dessa chamada reutilizam o que ele devolveu, por isso renove o token dentro dele quando estiver perto de expirar e o cliente nunca terá de ser reconstruído.
use OpenEmail\OpenEmail; $tokens = ['current' => 'token-from-your-oauth-flow']; $oauthClient = new OpenEmail(accessToken: fn(): string => $tokens['current']); $me = $oauthClient->me->get(); if ($me['object'] === 'oauth_token') { echo $me['clientId'], ' ', $me['expiresAt'], PHP_EOL;}| Caso | O que acontece |
|---|---|
| apiKey: e accessToken: juntos, ou nenhum | O cliente lança InvalidArgumentException quando é construído. Sem nenhum dos dois, a mensagem refere OPENEMAIL_API_KEY e OPENEMAIL_ACCESS_TOKEN. |
| Um valor que não é um token | Um token tem de 1 a 512 caracteres e não começa por oe_, a verificação que OpenEmail::isAccessToken() faz. Uma string que falha é recusada quando o cliente é construído, e um invocável que devolva uma assim faz a chamada lançar InvalidArgumentException antes de qualquer envio. |
| OPENEMAIL_ACCESS_TOKEN | Lido quando não passa nenhuma das duas credenciais e OPENEMAIL_API_KEY não está definida, por isso uma chave no ambiente tem prioridade. |
| Um invocável que lança uma exceção | A chamada lança essa mesma exceção, sem alterações, e nada é enviado. |
| Um apiKey: por chamada | Substitui o token nesse único pedido, e o invocável não é chamado. |
| $client->mode | Sempre live com um token. |
| OpenEmail::createTempMail() | Não envia nenhuma credencial, seja o que for que o ambiente contenha. |
| me->get() e me->ping() | Para um token, get responde com object igual a oauth_token, id e roleId a null, o clientId da aplicação ligada e expiresAt, o momento em que expira a aprovação que a pessoa deu à aplicação. ping responde com kind igual a oauth, keyId a null e o clientId. Verifique object ou kind antes de ler id ou keyId. |
Um token atua em nome de uma pessoa e lê o correio dela como ela o pode ler, por isso mantenha-o num servidor, tal como uma chave.
Códigos de verificação
Antes de uma alteração sensível, como apagar um domínio ou alterar um webhook, a API pede a um token de acesso o código de verificação que a aplicação web pediria à pessoa. A chamada lança um PermissionException, um 403 cujo isStepUpRequired() é true, e nada foi alterado. Peça um código, verifique o que a pessoa lhe der e depois faça a chamada de novo. A uma chave de API nunca é pedido.
use OpenEmail\Exception\ApiException; $domainId = 'b3e1f0a4-6c2d-4e8a-9f17-2d5c8a0b4e6f'; try { $client->domains->delete($domainId);} catch (ApiException $error) { if (!$error->isStepUpRequired()) { throw $error; } $challenge = $client->security->beginStepUp(); if ($challenge['method'] === 'email') { echo 'Enter the code we emailed to ', $challenge['sentTo'], PHP_EOL; } else { echo 'Enter the code from your authenticator app, or a backup code', PHP_EOL; } $client->security->verifyStepUp(['code' => trim((string) fgets(STDIN))]); $client->domains->delete($domainId);}| Método | O que faz |
|---|---|
| security->stepUpStatus() | Se a aplicação está verificada neste momento (elevated, elevatedUntil), como é verificado o próximo código (method, email ou totp) e minutes, a duração da janela. Não envia nada e não indica uma pausa. |
| security->beginStepUp() | Abre um desafio. Com email segue um código de seis dígitos para o endereço com que a pessoa inicia sessão, e sentTo mostra-o mascarado. Com totp a pessoa lê um na sua aplicação de autenticação ou usa um código de recuperação. Um desafio que ainda esteja aberto e tenha tentativas é reutilizado, a não ser que passe ['resend' => true], e um bloqueado ou expirado é substituído por uma chamada simples. Cada aplicação pode abrir 5 por hora e 20 em 24 horas para cada pessoa, e o seguinte lança um 429 step_up_throttled. |
| security->verifyStepUp(['code' => ...]) | Verifica o código e desbloqueia as alterações sensíveis para esta aplicação durante 60 minutos, até elevatedUntil, por REST e através das ferramentas MCP que fazem as mesmas alterações. Depois de 10 códigos errados em 24 horas desta aplicação, ou 20 de todas as aplicações da pessoa juntas, esta chamada e beginStepUp lançam um 429 step_up_locked com uma mensagem que diz quando a verificação é retomada. |
O cliente nunca pede um código nem repete a chamada por si só, e nenhum dos três métodos é repetido automaticamente, porque uma repetição depois de uma resposta perdida poderia enviar um segundo email ou gastar uma segunda tentativa. Não precisam de âmbito, e uma chave de API que chame um deles recebe um 400 step_up_not_applicable. OpenEmail\Constants\StepUpErrorCodes indica todas as formas como uma verificação pode falhar, e a página de erros da API diz o que fazer em cada caso.
O aviso de atualização
Quando há uma versão mais recente do pacote no Packagist, o cliente di-lo uma vez por processo, na saída de erro padrão, com uma linha como ℹ openemail/sdk 0.0.2 is available, you are on 0.0.1. seguida da página do pacote. A verificação só é executada na linha de comandos, quando a saída padrão é um terminal, e nunca num servidor web. Começa quando o primeiro cliente é construído e corre ao lado dos seus pedidos, e no fim do script espera pelo que resta de um orçamento de dois segundos. Uma falha ao chegar ao Packagist é ignorada.
A verificação faz o seu próprio pedido com cURL, fora do httpClient: do cliente, por isso um cliente HTTP falso num teste nunca o vê. Passe disableUpdateNotice: true ou defina OPENEMAIL_DISABLE_UPDATE_NOTICE para a desativar.
Proxies e TLS
O CurlHttpClient predefinido deixa os proxies a cargo do cURL, que lê https_proxy ou HTTPS_PROXY para o proxy e no_proxy ou NO_PROXY para os hosts que vão diretamente. Passe proxy: para indicar um no código. Um nome de utilizador e uma palavra-passe no URL do proxy são enviados ao proxy.
use OpenEmail\Http\CurlHttpClient;use OpenEmail\OpenEmail; $client = new OpenEmail(httpClient: new CurlHttpClient( caBundle: '/etc/ssl/certs/corporate-ca.pem', proxy: 'http://proxy.internal:3128', curlOptions: [CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4],));As ligações usam TLS 1.2 ou superior e verificam o certificado e o nome de host do servidor, e os redirecionamentos nunca são seguidos. caBundle: indica as autoridades de certificação em que confiar, para um proxy que inspeciona TLS. curlOptions: define qualquer outra opção do cURL, mas as definições de que um pedido precisa prevalecem sempre: o URL e a sua porta, o método, os cabeçalhos, o corpo e os redirecionamentos desligados. CURLOPT_REQUEST_TARGET é recusada, e uma opção que o cURL não aceite lança uma InvalidArgumentException que a indica.