Configuration
Comment construire un client, toutes les options, et ce qu'il refuse avant qu'une requête ne parte.
Options
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;| Point d'entrée | Ce que vous obtenez |
|---|---|
| new OpenEmail(...) | Un client construit à partir des arguments nommés que vous passez. Tout ce que vous omettez est lu dans l'environnement : la clé dans OPENEMAIL_API_KEY ou un jeton dans OPENEMAIL_ACCESS_TOKEN quand vous ne passez aucun identifiant, et l'URL de base dans OPENEMAIL_BASE_URL quand vous n'en passez pas. |
| OpenEmail::createClient(...) | Le même client que new OpenEmail(...), pour le code qui préfère appeler une fabrique. |
| OpenEmail::init(...) | Construit un client, le garde comme client partagé et le renvoie. Il prend les mêmes arguments nommés. |
| OpenEmail::getClient() | Le client partagé, depuis n'importe où dans le processus. Appelé avant init, il en construit un à partir de l'environnement au premier appel. |
| OpenEmail::resetClient() | Abandonne le client partagé : le prochain getClient() en construit un nouveau, ce que veut un test entre deux cas. |
Convertir getenv() en chaîne est voulu. Une variable non définie devient une clé vide, que le client refuse avec un message nommant la variable dont il a besoin, là où null se rabattrait silencieusement sur 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,);| Option | Par défaut | Remarques |
|---|---|---|
| apiKey: | OPENEMAIL_API_KEY | Doit commencer par oe_live_ ou oe_test_. N'est lue dans l'environnement que si vous ne passez ni apiKey: ni accessToken:. |
| accessToken: | OPENEMAIL_ACCESS_TOKEN | Un jeton d'accès OAuth, ou un callable qui en renvoie un. Voir Jetons d'accès OAuth plus bas. Passez une clé ou un jeton, jamais les deux. |
| baseUrl: | https://api.openemail.uk | Ou OPENEMAIL_BASE_URL. Les barres obliques finales sont supprimées, et https:// est ajouté devant un hôte nu, ou http:// devant un hôte de cette machine : localhost, une adresse 127.x.x.x ou ::1. Un identifiant n'est jamais envoyé en http simple vers un autre hôte, et 0.0.0.0 ou [::] est refusé à la construction du client, car ce sont des adresses sur lesquelles un serveur écoute, pas des adresses auxquelles envoyer des requêtes. |
| timeout: | 30 | Secondes par tentative, pas par appel, couvrant la connexion et la lecture de la réponse entière. 0 le désactive. files->upload attend au moins 600 secondes, sauf si vous passez timeout: sur cet appel. |
| maxRetries: | 2 | Tentatives supplémentaires après la première, sur les appels qui peuvent être répétés sans risque. Se règle sur le client, pas par appel. 0 désactive les réessais. |
| httpClient: | CurlHttpClient | La couche HTTP : tout ce qui implémente OpenEmail\Http\HttpClient, comme Psr18HttpClient autour de Guzzle ou de Symfony HttpClient, ou un faux dans un test. La page Clients HTTP couvre chacun. |
| headers: | [] | Envoyés à chaque requête. |
| userAgent: | openemail-php/<version> | Envoyés à chaque requête. |
| disableUpdateNotice: | false | Ignore la vérification, faite une fois par processus, d'une version plus récente sur Packagist. La vérification ne s'exécute qu'en ligne de commande lorsque la sortie standard est un terminal, et OPENEMAIL_DISABLE_UPDATE_NOTICE la désactive aussi. |
Variables d'environnement
| Variable | Ce qu'il fait |
|---|---|
| OPENEMAIL_API_KEY | La clé qu'utilise un client quand vous ne passez ni apiKey: ni accessToken:. |
| OPENEMAIL_ACCESS_TOKEN | Un jeton d'accès OAuth, lu seulement quand vous ne passez aucun des deux identifiants et que OPENEMAIL_API_KEY n'est pas définie : une clé présente dans l'environnement l'emporte donc. |
| OPENEMAIL_BASE_URL | L'URL de base quand vous n'en passez aucune. Un hôte nu comme localhost:2222 reçoit son schéma. |
| OPENEMAIL_DISABLE_UPDATE_NOTICE | Toute valeur non vide désactive l'avis de mise à jour, pour tous les clients du processus. |
| HTTPS_PROXY et NO_PROXY, ou https_proxy et no_proxy | Le proxy par lequel se connecte cURL, et les hôtes joints directement. Voir Proxys et TLS plus bas. |
Chaque variable est lue d'abord avec getenv(), puis dans $_SERVER et $_ENV : une valeur que votre framework a chargée depuis un fichier .env compte donc aussi. Une variable définie mais vide compte comme non définie.
Ce qu'il refuse avant d'envoyer
Ces cas lèvent OpenEmail\Exception\InvalidArgumentException depuis la ligne qui contenait la mauvaise valeur, au lieu d'apparaître comme un échec déroutant lors de votre premier envoi. Le message indique ce qui n'allait pas et ce qu'il faut passer à la place, et il ne répète jamais un identifiant.
| Refusé | Pourquoi |
|---|---|
| Aucun identifiant | Ni apiKey: ni accessToken: n'a été passé, et aucune des deux variables n'était définie : il n'y a donc rien pour s'authentifier. Levée à la construction du client. |
| Une clé et un jeton ensemble | Chaque requête porte un seul identifiant : le client ne peut donc pas savoir lequel vous vouliez. |
| Un cookie de session, un jeton de session ou une clé d'un autre service | Seuls oe_live_ et oe_test_ authentifient ici, et l'API le dit aussi. La vérification porte sur le préfixe et rien de plus : une clé révoquée échoue donc quand même au moment de la requête, sous la forme d'une AuthenticationException. |
| Une baseUrl: qui n'est pas une URL http ou https, ou qui contient un nom d'utilisateur ou un mot de passe | Rien d'autre n'est joignable, et un identifiant a sa place dans apiKey: ou accessToken:, pas dans l'URL. Levée à la construction du client. |
| Un identifiant envoyé en http simple vers un hôte qui n'est pas sur cette machine | Levée par l'appel, avant tout envoi. Utilisez une URL de base en https. |
| Un timeout: négatif | Passez des secondes, ou 0 pour aucun délai. Levée à la construction du client, ou par l'appel pour un délai passé à un seul appel. |
| Un nom d'en-tête qui n'est pas un token valide, ou un saut de ligne ou un autre caractère de contrôle dans une valeur d'en-tête | Vérifié dans headers:, userAgent: et idempotencyKey:, car un saut de ligne commencerait un deuxième en-tête. Les espaces, tabulations et sauts de ligne autour d'une valeur sont d'abord retirés, comme le fait fetch : une clé lue dans un fichier qui se termine par un saut de ligne fonctionne donc quand même. |
| Un id vide ou fait uniquement de points sur n'importe quelle méthode | Levée à l'appel de la méthode. Un segment de chemin fait de points est supprimé par tous les analyseurs d'URL : la requête atteindrait donc un autre endpoint. Un id qui n'est pas de l'UTF-8 valide est également refusé. |
| Un contenu de pièce jointe qui n'est pas en base64 | Une chaîne est toujours lue comme du base64 : des octets bruts dedans seraient donc envoyés comme des données illisibles. Encodez-les avec OpenEmail::toBase64(), ou passez un SplFileInfo, un flux ou un flux PSR-7 et le client les encode. |
La classe étend l'InvalidArgumentException propre à PHP : le code qui l'intercepte déjà continue donc de fonctionner, et elle implémente OpenEmail\Exception\OpenEmailException comme toute autre exception que lève le package. Une valeur du mauvais type, comme un nombre là où va un id sous forme de chaîne, est une TypeError de PHP lui-même, car chaque méthode déclare ses types.
Il n'existe pas d'option testMode: et il n'y en aura pas. Le schéma de la clé fait partie de l'identifiant et n'est pas un simple indice : le mode est donc une propriété de la clé. $client->mode lit le préfixe, live ou test, et ne décide de rien.
Un client, plusieurs clés
Construisez le client une fois et partagez-le. Un nouveau client par requête jette sa connexion ouverte pour rien, et aucun de ses états n'est propre à un appelant.
Pour le cas qui imposerait sinon un client par clé, comme un job qui envoie pour le compte de plusieurs espaces de travail, passez apiKey: sur l'appel. Il remplace l'en-tête Authorization pour cette requête et ne laisse rien derrière lui sur le client.
$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);Toute méthode hors de tempMail le prend comme dernier argument nommé, après les filtres d'une liste, et les méthodes de tempMail prennent inboxToken: à la place. Il est vérifié avant l'envoi de la requête, selon la même règle que celle du client : une faute de frappe lève donc une InvalidArgumentException portant sur l'apiKey passée à cet appel, plutôt qu'un 401 sur un identifiant qu'il vous faudrait ensuite retrouver. Un appel réessayé garde la clé qui lui a été donnée.
$client->mode décrit la clé avec laquelle le client a été CONSTRUIT et ne suit pas une substitution. Dès qu'un client sert plusieurs clés, il n'y a plus de mode unique à signaler : lisez-le donc sur la clé que vous avez passée. var_dump($client) affiche le mode et l'URL de base, jamais la clé, et chaque paramètre qui reçoit un identifiant est marqué #[\SensitiveParameter] : une trace de pile affiche donc un substitut à sa place.
Endpoints qu'aucune méthode n'encapsule
$client->raw est le transport par lequel passe chaque méthode. $client->raw->request() appelle un chemin qu'aucune méthode n'encapsule encore, en appliquant l'identifiant, l'URL de base, le délai et la politique de réessai du client, et renvoie le corps décodé comme le fait une méthode.
$ping = $client->raw->request('/ping'); $label = $client->raw->request('/labels', method: 'POST', body: ['name' => 'Invoices']); var_dump($ping, $label);| Argument nommé | Ce qu'il fait |
|---|---|
| method: | GET sauf indication contraire : POST, PUT, PATCH ou DELETE. |
| query: | Un tableau de paramètres de requête. Les valeurs null et vides sont omises, une liste est jointe par des virgules, et un DateTimeInterface est envoyé comme un instant ISO 8601 en UTC. |
| body: | Un tableau, envoyé en JSON. |
| raw: et contentType: | Des octets à envoyer tels quels, sous forme de chaîne, de ressource de flux, de SplFileInfo ou de flux PSR-7, avec application/octet-stream sauf si vous indiquez un type. |
| accept: et binary: | Un accept: autre que JSON renvoie le corps sous forme de texte, et binary: true le renvoie sous forme de chaîne d'octets. |
| idempotent: et idempotencyKey: | idempotent: true attache un Idempotency-Key, généré sauf si vous passez le vôtre. |
| repeatable: | Indique si un échec est réessayé. Seul un GET l'est, sauf si vous passez repeatable: true. |
| anonymous: | true n'envoie aucun identifiant. |
| apiKey:, inboxToken: et timeout: | Les mêmes identifiants par appel, et un délai en secondes pour ce seul appel. |
Le chemin doit commencer par un seul /, et un chemin dont l'URL finale quitterait l'origine de l'URL de base lève InvalidArgumentException avant tout envoi : l'identifiant n'atteint donc jamais un autre hôte.
Boîtes jetables
OpenEmail::createTempMail() construit un client pour boîtes jetables qui ne porte aucune clé API et n'en lit aucune dans l'environnement. Il crée des boîtes anonymement, et chaque lecture envoie le jeton de boîte renvoyé par create, ou le plus récent renvoyé par extend, soit par appel via inboxToken:, soit une fois via 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() prend baseUrl:, httpClient:, maxRetries:, timeout:, userAgent:, headers: et disableUpdateNotice: comme n'importe quel client, et lit OPENEMAIL_BASE_URL quand vous ne passez pas d'URL de base.
Jetons d'accès OAuth
Une application qu'une personne a connectée en OAuth, comme un outil en ligne de commande ou un agent, détient un jeton d'accès plutôt qu'une clé API. Passez-le comme accessToken:, soit le jeton lui-même, soit un callable qui le renvoie, comme une closure ou un callable de première classe. Le callable s'exécute une fois par appel, et les réessais de cet appel réutilisent ce qu'il a renvoyé : renouvelez donc le jeton à l'intérieur quand il approche de son expiration, et le client n'aura jamais à être reconstruit.
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;}| Cas | Ce qui se passe |
|---|---|
| apiKey: et accessToken: ensemble, ou aucun des deux | Le client lève InvalidArgumentException à sa construction. Sans aucun des deux, le message nomme OPENEMAIL_API_KEY et OPENEMAIL_ACCESS_TOKEN. |
| Une valeur qui n'est pas un jeton | Un jeton fait de 1 à 512 caractères et ne commence pas par oe_, la vérification que fait OpenEmail::isAccessToken(). Une chaîne qui ne la passe pas est refusée à la construction du client, et un callable qui en renvoie une fait lever InvalidArgumentException à l'appel, avant tout envoi. |
| OPENEMAIL_ACCESS_TOKEN | Lu quand vous ne passez aucun des deux identifiants et que OPENEMAIL_API_KEY n'est pas définie : une clé dans l'environnement l'emporte donc. |
| Un callable qui lève une exception | L'appel lève cette exception, inchangée, et rien n'est envoyé. |
| Une apiKey: par appel | Remplace le jeton pour cette seule requête, et le callable n'est pas appelé. |
| $client->mode | Toujours live avec un jeton. |
| OpenEmail::createTempMail() | N'envoie aucun identifiant, quel que soit le contenu de l'environnement. |
| me->get() et me->ping() | Pour un jeton, get répond avec object à oauth_token, id et roleId à null, le clientId de l'application connectée, et expiresAt, le moment où l'autorisation donnée par la personne à l'application expire. ping répond avec kind à oauth, keyId à null et le clientId. Vérifiez object ou kind avant de lire id ou keyId. |
Un jeton agit pour une personne et lit son courrier comme elle le peut : gardez-le donc sur un serveur, comme une clé.
Codes de vérification
Avant un changement sensible, comme supprimer un domaine ou modifier un webhook, l'API demande à un jeton d'accès le code de vérification que l'application web demanderait à la personne. L'appel lève une PermissionException, un 403 dont isStepUpRequired() vaut true, et rien n'a été modifié. Demandez un code, vérifiez celui que la personne vous donne, puis refaites l'appel. On ne le demande jamais à une clé API.
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éthode | Ce qu'il fait |
|---|---|
| security->stepUpStatus() | Si l'application est vérifiée en ce moment (elevated, elevatedUntil), comment le prochain code est contrôlé (method, email ou totp), et minutes, la durée de la fenêtre. N'envoie rien et ne signale pas de pause. |
| security->beginStepUp() | Ouvre une vérification. Avec email, un code à six chiffres part vers l'adresse avec laquelle la personne se connecte, et sentTo l'affiche masquée. Avec totp, elle en lit un dans son application d'authentification ou utilise un code de récupération. Une vérification encore ouverte à laquelle il reste des essais est réutilisée, sauf si vous passez ['resend' => true], et une vérification verrouillée ou expirée est remplacée par un simple appel. Chaque application peut en ouvrir 5 par heure et 20 en 24 heures pour chaque personne, et la suivante lève un 429 step_up_throttled. |
| security->verifyStepUp(['code' => ...]) | Contrôle le code et débloque les changements sensibles pour cette application pendant 60 minutes, jusqu'à elevatedUntil, par REST et par les outils MCP qui font les mêmes changements. Après 10 codes erronés en 24 heures venant de cette application, ou 20 venant de toutes les applications de la personne ensemble, cet appel et beginStepUp lèvent un 429 step_up_locked avec un message qui indique quand la vérification reprend. |
Le client ne demande jamais de code et ne refait jamais l'appel de lui-même, et aucune des trois méthodes n'est réessayée automatiquement, car une nouvelle tentative après une réponse perdue pourrait envoyer un deuxième e-mail ou consommer un deuxième essai. Elles ne demandent aucune portée, et une clé API qui en appelle une reçoit un 400 step_up_not_applicable. OpenEmail\Constants\StepUpErrorCodes nomme chaque façon dont une vérification peut échouer, et la page des erreurs de l'API indique quoi faire dans chaque cas.
L'avis de mise à jour
Quand une version plus récente du package est disponible sur Packagist, le client le signale une fois par processus, sur la sortie d'erreur standard, par une ligne comme ℹ openemail/sdk 0.0.2 is available, you are on 0.0.1. suivie de la page du package. La vérification ne s'exécute qu'en ligne de commande, quand la sortie standard est un terminal, et jamais sous un serveur web. Elle démarre à la construction du premier client et s'exécute à côté de vos requêtes, et à la fin du script elle attend ce qui reste d'un budget de deux secondes. Un échec pour joindre Packagist est ignoré.
La vérification fait sa propre requête avec cURL, en dehors du httpClient: du client : un faux client HTTP dans un test ne la voit donc jamais. Passez disableUpdateNotice: true ou définissez OPENEMAIL_DISABLE_UPDATE_NOTICE pour la désactiver.
Proxys et TLS
Le CurlHttpClient par défaut laisse les proxys à cURL, qui lit https_proxy ou HTTPS_PROXY pour le proxy et no_proxy ou NO_PROXY pour les hôtes joints directement. Passez proxy: pour en nommer un dans le code à la place. Un nom d'utilisateur et un mot de passe dans l'URL du proxy sont envoyés au 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],));Les connexions utilisent TLS 1.2 ou plus récent et vérifient le certificat et le nom d'hôte du serveur, et les redirections ne sont jamais suivies. caBundle: nomme les autorités de certification auxquelles faire confiance, pour un proxy qui inspecte TLS. curlOptions: définit toute autre option cURL, mais les réglages dont une requête a besoin l'emportent toujours : l'URL et son port, la méthode, les en-têtes, le corps et les redirections désactivées. CURLOPT_REQUEST_TARGET est refusée, et une option que cURL n'accepte pas lève une InvalidArgumentException qui la nomme.