Aller à la documentation
PHP

Envoyer un e-mail

`emails->send` : un message, maintenant ou plus tard.

emails->send

send_email.php
$email = $client->emails->send([    'from' => ['email' => '[email protected]', 'name' => 'Acme Billing'],    'to' => ['[email protected]', 'Grace <[email protected]>'],    'cc' => '[email protected]',    'bcc' => [['email' => '[email protected]']],    'replyTo' => '[email protected]',    'subject' => 'Your September invoice',    'html' => '<p>Invoice attached.</p>',    'text' => 'Invoice attached.',    'headers' => ['X-Campaign' => 'invoices'],    'attachments' => [['filename' => 'invoice.pdf', 'content' => new \SplFileInfo('invoice.pdf')]],    'threadId' => 'CAHk7pQ2x9LmZ4-mail.example.com',    'scheduledAt' => 'PT1H',    'tags' => ['order' => '4021'],    'tracking' => ['opens' => true, 'clicks' => true],]); echo $email['id'], ' ', $email['status'], PHP_EOL;

to, cc et bcc acceptent un destinataire ou une liste de destinataires, et un destinataire seul est enveloppé pour vous. Chacun peut être une adresse nue, Name <addr@host>, ou un tableau avec email et name.

Le message est un tableau dont les clés sont les noms de champ de l'API, c'est pourquoi replyTo et scheduledAt restent en camelCase, alors que idempotencyKey: et apiKey: sont des arguments nommés de l'appel et ne font jamais partie du message. Pour changer un champ d'un message construit plus tôt, décompressez-le dans un nouveau tableau : $client->emails->send([...$message, 'subject' => 'Re: your invoice']) garde tous les autres champs et remplace l'objet.

Paramètres

fromstring or arrayobligatoire
L'expéditeur. Une adresse nue, `Name <addr@host>`, ou un tableau avec `email` et `name`. Ce doit être une adresse depuis laquelle cette clé peut envoyer, sinon l'appel lève un 403 `from_address_forbidden`. Il n'y a pas d'expéditeur de repli : un envoi nomme donc toujours l'adresse depuis laquelle il part.
tostring or arrayobligatoire
Un destinataire ou une liste de destinataires, et un destinataire seul est enveloppé pour vous. 50 au maximum pour `to`, `cc` et `bcc` réunis, et au-delà c'est un 422 `too_many_recipients`.
ccstring or array
Compte dans la limite de 50 destinataires.
bccstring or array
Jamais nommé dans les octets que reçoivent les autres, car une enveloppe est transmise par destinataire. Compte aussi dans les 50.
replyTostring or array
Une seule adresse, envoyée comme en-tête Reply-To.
subjectstring
998 caractères au maximum, la limite de ligne de la RFC 5322. Vide par défaut, et un objet vide se rabat sur celui du modèle ou du brouillon.
htmlstring
L'un de `html`, `text`, `draftId` ou `template` est obligatoire. C'est le HTML que voient les destinataires quand `html` et `text` sont tous deux fournis. 1 000 000 de caractères au maximum.
textstring
La partie en texte brut, 1 000 000 de caractères au maximum.
templatearray
Rend un modèle stocké côté serveur : un tableau avec `id`, qui accepte un id ou un slug, et optionnellement `version` (un int), `props` et `slots`. `version` fige une révision. Omettez-la pour utiliser ce qui est publié au moment où la requête est acceptée. Une prop inconnue ou manquante donne un 422 plutôt qu'un blanc dans le message.
draftIdstring
Envoie un brouillon enregistré sous cette enveloppe, tel qu'il a été écrit. Ne peut pas être combiné avec `template` ou `translate`.
headersarray
Nom d'en-tête vers valeur chaîne, limité à `X-*`, `List-*`, Reply-To, Precedence, Auto-Submitted, Importance, Priority et Feedback-ID. Tout ce que le transport définit lui-même est refusé avec un 422 `reserved_header` plutôt qu'ignoré en silence.
attachmentsarray
Une liste, chaque entrée étant un tableau avec `filename`, `content` et un `contentType` optionnel, ou un tableau avec seulement `fileId`, désignant un fichier déjà présent dans l'espace de travail, comme un fichier issu de `files->upload`. `content` est en base64 : un flux issu de `fopen`, un `SplFileInfo` ou un flux PSR-7 est lu et encodé pour vous, et une chaîne doit déjà être en base64. 20 fichiers, les fichiers inline étant plafonnés à 5 Mo au total une fois décodés. Un fichier stocké peut être plus gros et voyage sous forme de lien de téléchargement.
attachmentDeliverystring
`mime`, `link` ou `auto`. `auto` transporte les fichiers sous forme de liens de téléchargement dès qu'ils dépassent 2 Mo sur un domaine disposant d'un domaine de fichiers actif, et à l'intérieur du message sinon. Omis, c'est le réglage de la boîte qui s'applique, et il vaut `auto` par défaut.
threadIdstring
Répond dans un thread existant. Le transport écrit In-Reply-To et References.
scheduledAtDateTimeInterface or string
Un `DateTimeInterface`, envoyé comme un instant ISO 8601 en UTC, un instant ISO 8601 sous forme de chaîne, ou une durée comme `PT1H`. Jusqu'à un an à l'avance, jamais dans le passé. Ne peut pas être combiné avec `cancellableForSeconds`. Une chaîne de date sans heure, comme `2027-01-01`, est lue comme minuit UTC ce jour-là : passez donc un instant quand l'heure compte.
cancellableForSecondsint
De 0 à 900. Une fenêtre d'annulation sur un envoi immédiat : le mécanisme d'annulation du composer, exposé plutôt que codé en dur.
tagsarray
Jusqu'à 10 libellés, avec des clés de 1 à 64 lettres, chiffres, `_` ou `-` et des valeurs chaîne jusqu'à 256 caractères. Renvoyés tels quels à chaque lecture et jamais interprétés.
signaturebool
Indique si ce message porte la signature de l'adresse d'envoi : la sienne, sinon celle du catch-all pour une adresse captée par un catch-all, sinon le pied de page OpenEmail, sauf si cette adresse l'a désactivé. Sans valeur, un corps `html` part exactement tel qu'écrit, sans signature, et un corps uniquement `text` la porte. Mettez-le à false pour le courrier qu'un programme envoie au nom de quelqu'un, comme un reçu, une réinitialisation de mot de passe ou un récapitulatif, dont aucun ne veut de la signature d'une personne. Les envois de modèles et les envois chiffrés n'en portent jamais.
trackingarray
Un tableau avec `opens` et `clicks` optionnels, chacun un bool : indique s'il faut ajouter un pixel d'ouverture et réécrire les liens pour ce message. Désactivé sauf si le suivi a été activé pour l'adresse d'expédition (ou le catch-all qui l'a attrapée), et chacune des deux clés indiquée ici tranche pour ce seul message, quel que soit le réglage de l'adresse.
translatearray
L'envoie dans la langue du destinataire : un tableau avec `to` et optionnellement `from`, `subject` et `includeOriginal`. `to` accepte un code, un nom anglais ou le nom de la langue dans sa propre langue, et `subject` et `includeOriginal` valent true par défaut. Fixé au moment où la requête est acceptée, pour qu'un message planifié porte les mots qui ont été approuvés. Refusé en présence de `draftId`.
idempotencyKeystring
Un argument nommé de l'appel plutôt qu'un champ du message. Votre propre clé pour cet envoi, de 1 à 255 caractères parmi les lettres, les chiffres, `_`, `.`, `:` ou `-`. Sans elle, le client génère une clé pour chaque appel, afin que ses propres réessais n'envoient jamais deux fois, et avec elle, un envoi qui s'exécute de nouveau dans un autre processus est rejoué au lieu d'être répété.
apiKeystring
Un argument nommé, lui aussi. Envoie avec cette clé au lieu de celle du client, pour un processus qui envoie pour le compte de plusieurs espaces de travail.

Réponse

Un tableau dont les clés sont les noms camelCase de l'API : $email['status'] lit donc le statut.

idstring
L'id de l'envoi, `msg_` suivi de 24 caractères hexadécimaux. Utilisez-le pour `get`, `cancel`, `reschedule` et `getTracking`.
statusstring
queued, scheduled, sending, sent, partial, bounced, cancelled ou failed. Lisez ceci plutôt que le fait que l'appel a retourné : un envoi immédiat est expédié pendant la requête et revient généralement en `sent`, `partial` ou `failed`, et un envoi retenu revient en `queued` ou `scheduled`. `partial` est un état à part entière : certains destinataires l'ont reçu et on ne peut pas le leur reprendre, réessayer est donc une erreur et annoncer un échec est un mensonge.
modestring
`live` ou `test` : le type de clé qui l'a envoyé. Un envoi de test est enregistré et jamais transmis. Il affiche `sent`, avec `transport` à `test` : faites donc vos assertions sur la réponse et non sur une boîte de réception.
fromstring
L'adresse réellement autorisée et placée sur le réseau, qui n'est pas toujours celle demandée.
subjectstring or null
Tel qu'envoyé.
messageIdstring or null
Le Message-ID RFC 5322. null tant que le MIME n'existe pas. Le service d'envoi réécrit l'en-tête au départ : aucun bounce ni rapport de distribution ne porte donc cette valeur. C'est sur `id` qu'un événement revient.
threadIdstring or null
Le thread dans lequel il a atterri.
transportstring or null
Par où le message est parti. null jusqu'à l'expédition.
attemptsint
Combien de fois l'expédition a été tentée.
lastErrorstring or null
Pourquoi la dernière tentative a échoué, mot pour mot.
scheduledAtstring or null
L'instant ISO 8601 auquel il doit partir.
cancellableUntilstring or null
Tant que l'heure actuelle est antérieure, `cancel` fonctionne encore.
sentAtstring or null
L'instant ISO 8601 auquel il est parti.
tagsarray
Ce que vous avez envoyé, renvoyé tel quel.
sourcestring
composer, api, mcp, ai ou queue : quelle surface a demandé l'envoi. `api`, c'est ce client.
createdAtstring
L'instant ISO 8601 auquel l'enregistrement a été écrit.
replayedbool
Vrai quand une Idempotency-Key correspondait à un envoi qui existait déjà. Rien de nouveau n'a été envoyé, et voici le message d'origine dans son état actuel.
translationarray
Présent uniquement sur un message qui a été traduit, et uniquement là où toute la requête stockée est transportée : cette réponse et `get`. Il contient `language`, `languageName`, `detectedSourceLanguage`, `subject` et `includeOriginal`, avec des codes plutôt que des lignes de langue complètes. Une ligne de liste ne le porte jamais : son absence là-bas ne dit rien, ni dans un sens ni dans l'autre.

Dans la langue du destinataire

translate rédige le message dans la langue de quelqu'un d'autre avant son départ. Le corps, et l'objet sauf si vous le désactivez, est traduit au moment où l'API accepte la requête, et ce qui en est sorti est ce qui part : une traduction qui n'a pas pu être produite fait refuser l'envoi plutôt que d'expédier le message dans la langue où vous l'avez écrit.

translate.php
$email = $client->emails->send([    'from' => '[email protected]',    'to' => '[email protected]',    'subject' => 'Your September invoice',    'html' => '<p>Invoice attached. Payment is due on the 14th.</p>',    'translate' => ['to' => 'de'],]); print_r($email['translation'] ?? []);

$email['translation'] contient alors language à de, languageName à German, detectedSourceLanguage à en, et subject et includeOriginal tous deux à true.

Personne n'a relu ce texte avant son départ. emails->translate est le même aller-retour, arrêté une étape plus tôt. Montrez-le à une personne, laissez-la le modifier, puis envoyez ce qu'elle a approuvé sans aucun translate sur l'appel. Le repasser traduirait une deuxième fois et jetterait ses corrections.

preview_translation.php
$preview = $client->emails->translate([    'subject' => 'Your September invoice',    'html' => '<p>Invoice attached. Payment is due on the 14th.</p>',    'to' => 'de',]); echo $preview['language']['native'], PHP_EOL, $preview['subject'], PHP_EOL, $preview['html'], PHP_EOL;echo 'Send it as it is? [y/N] '; $answer = fgets(STDIN); if ($answer !== false && strtolower(trim($answer)) === 'y') {    $client->emails->send([        'from' => '[email protected]',        'to' => '[email protected]',        'subject' => $preview['subject'],        'html' => $preview['html'],    ]);}
languages.php
use OpenEmail\Constants\Languages;use OpenEmail\OpenEmail; echo count(Languages::ALL), PHP_EOL; $current = $client->languages->list();echo count($current), PHP_EOL; echo OpenEmail::resolveLanguage('Deutsch')['code'] ?? 'none', PHP_EOL;echo OpenEmail::resolveLanguage('zh-TW')['code'] ?? 'none', PHP_EOL;echo OpenEmail::languageByCode('DE')['native'] ?? 'none', PHP_EOL;var_dump(OpenEmail::isRtlLanguage('ar'));

Ces lignes affichent 200, le nombre de lignes livrées avec cette version, puis le nombre que l'API contient maintenant, puis de, zh-Hant, Deutsch et bool(true). La table est embarquée, dans l'ordre du sélecteur, sous la forme OpenEmail\Constants\Languages::ALL, une liste de tableaux avec code, label, native, flag et rtl, pour qu'un sélecteur puisse être rempli avant la première requête. languages->list renvoie les mêmes lignes depuis le réseau sous forme de simple liste, pour un appelant qui préfère les lignes actuelles à celles livrées avec cette version. OpenEmail::resolveLanguage() accepte un code, un nom anglais, un endonyme ou un alias (zh-TW est l'alias d'un code qui n'est plus listé) et renvoie null quand rien ne correspond, OpenEmail::languageByCode() trouve un code exact sans tenir compte de la casse, et OpenEmail::isRtlLanguage() indique si une langue s'écrit de droite à gauche, comme c'est le cas de seize des lignes. Cherchez dans native, label et code ensemble, affichez native en premier, et stockez le code.

emails->translate n'est pas réessayé automatiquement. Il consomme des appels au modèle et n'écrit rien : il n'y a donc rien à rendre idempotent, et une retentative après une requête restée sans réponse ne ferait qu'acheter deux fois la même réponse.

  • Une langue que l'API ne peut pas identifier donne une validation_error sur translate.to, avant tout envoi.
  • translation_too_long au-delà de 30 000 caractères, translation_not_configured quand l'installation n'a aucune IA configurée, un 429 ai_quota_exceeded quand l'espace de travail a épuisé les actions IA du jour (il se réinitialise à minuit UTC et n'est pas réessayé), translation_failed quand le fournisseur n'a pas répondu. Aucun d'eux n'envoie le message non traduit en guise de repli.
  • Fonctionne avec template : c'est la sortie RENDUE qui est traduite, si bien qu'un seul corps stocké sert toutes les langues que lisent vos clients. Un template qui rend un document entier conserve son doctype, ses blocs <style> et ses règles @font-face : seul le corps part vers le modèle et le reste est remis autour. Son <title> est laissé tel quel, et rien ne l'affiche de toute façon.
  • Une retentative ne coûte rien de plus. La traduction ne fait pas partie de l'empreinte d'idempotence (la requête, elle, en fait partie, translate compris) : réessayer un envoi resté sans réponse avec la même Idempotency-Key rejoue donc le message qui existe déjà au lieu d'en traduire et d'en envoyer un second.
  • Un message traduit qui est en file d'attente ou planifié garde la formulation approuvée. emails->reschedule le déplace toujours, tandis que emails->update refuse une nouvelle formulation avec un 409 translation_locked : changer ce qu'il dit implique donc de l'annuler et de l'envoyer à nouveau.

Pièces jointes

content est en base64 sur le réseau. Donnez au client quelque chose qu'il peut lire et il encode les octets pour vous : une ressource de flux issue de fopen, un SplFileInfo, ou un flux ou un fichier téléversé PSR-7. Une chaîne est envoyée telle quelle : elle doit donc déjà être en base64, ce que OpenEmail::toBase64() fait des octets que vous avez en mémoire.

attachments.php
use OpenEmail\OpenEmail; $attachments = [    ['filename' => 'invoice.pdf', 'content' => OpenEmail::toBase64(file_get_contents('invoice.pdf')), 'contentType' => 'application/pdf'],    ['filename' => 'report.csv', 'content' => new \SplFileInfo('report.csv')],    ['filename' => 'contacts.csv', 'content' => fopen('contacts.csv', 'rb')],    ['fileId' => 'file_6bb640f5b99e47deb758f1f5'],]; $client->emails->send([    'from' => '[email protected]',    'to' => '[email protected]',    'subject' => 'Your documents',    'text' => 'All three are attached.',    'attachments' => $attachments,]);

Un content sous forme de chaîne qui n'est pas du base64 lève OpenEmail\Exception\InvalidArgumentException avant tout envoi. Des octets bruts qui se trouvent être lisibles comme du base64 partiraient au contraire altérés : ne passez donc jamais les octets d'un fichier tels quels. Enveloppez-les dans OpenEmail::toBase64(), ou passez le fichier lui-même.

OpenEmail::toBase64() est là si vous avez besoin du même encodage ailleurs. Il prend une chaîne d'octets, une ressource de flux, un SplFileInfo ou un flux PSR-7 et renvoie du base64 sans saut de ligne.