Контакты
`contacts->list`, `get`, `create`, `save`, `update`, `setAudiences`, `delete`, `deleteMany`, `listPeople`, `setPhoto`, `removePhoto`, `block`, `unblock`, `listThreads` и `activity`.
Все методы
$page = $client->contacts->list(limit: 100);$contact = $client->contacts->get('[email protected]'); $saved = $client->contacts->create([ 'email' => '[email protected]', 'name' => 'Grace Hopper', 'notes' => 'Met at the compiler workshop',]); $client->contacts->update('[email protected]', ['notes' => null]);$client->contacts->setAudiences('[email protected]', ['audienceIds' => ['aud_4c1b8e2a7d9f05c36b4e8a71']]);$client->contacts->delete('[email protected]'); echo count($page), ' ', $page->hasMore ? 'more to come' : 'that is all', PHP_EOL;echo $contact['source'], ' ', $contact['lastSeenAt'] ?? 'never mailed', ' ', $saved['source'], PHP_EOL;list возвращает сначала контакты, замеченные недавно, а контакты, которым никогда не писали, в конце. source равен auto, когда строка записана потому, что участник отправил на этот адрес сообщение из композера приложения, а это существенно иное утверждение, чем то, что кто-то его сохранил. Почта, приходящая с адреса, ничего не записывает, как и отправка через этот API.
Адресная книга принадлежит рабочему пространству, а не одному человеку, поэтому контакт, сохранённый любым участником, является тем же контактом, который видят все участники и все ключи. create записывает source как manual и сразу при записи помещает контакт в аудиторию по умолчанию. Назовите собственные списки в audienceIds, чтобы добавить контакт в них тем же вызовом (для этого также нужна audiences:write), или добавьте контакт позже через audiences->addContact, описанный на странице об аудиториях. setAudiences одним вызовом точно задаёт, в каких списках состоит контакт.
Адреса хранятся в нижнем регистре, а клиент кодирует переданный вами адрес, поэтому [email protected] попадает в нужную строку. Пустой адрес выбрасывает InvalidArgumentException ещё до отправки. Адрес является идентичностью контакта, поэтому update не может его изменить: перенос контакта означает delete и create.
Параметры: contacts->list
limitint- Сколько контактов возвращать на странице: целое число от 1 до 200, по умолчанию 50. Значение вне диапазона даёт 422, а не подрезается. Аргумент имеет тип `int`, поэтому значение, прочитанное из строки запроса, сначала приведите через `(int)`.
cursorstring- `nextCursor` предыдущей страницы. Никогда не составляйте его сами: курсор, который называет уже не существующий контакт, даёт 400 `invalid_cursor`, выбрасываемый как `InvalidRequestException`. Это означает, что ваше состояние постраничного обхода устарело и обход нужно начать заново без курсора.
sourcestring- `manual` для контактов, которые кто-то сохранил намеренно, `auto` для тех, что записал композер приложения. Не указывайте, чтобы получить всю книгу.
qstring- Ищет по имени и адресу, до 200 символов. Если на первой странице ничто не совпадает точно, вместо этого возвращаются близкие написания, и следующие страницы продолжают искать так же.
Ответ: контакт
contacts->list возвращает OpenEmail\Result\Page, поэтому строки лежат в $page->items, а обход идёт по $page->nextCursor, пока $page->hasMore истинно. listAll возвращает все строки одним массивом, а iterate возвращает Generator, который выдаёт их по одной. get, create, update, save и setAudiences возвращают по одному контакту как массив с ключами в camelCase: та же строка плюс audiences. Адресная книга не ограничена по размеру, поэтому этот маршрут отдаёт данные постранично, а не возвращает массив, который молча обрывается на 200.
objectstring- Всегда строка `contact` как в строках списка, так и в `get`.
emailstring- Адрес, приводимый к нижнему регистру при записи, так что `[email protected]` и `[email protected]` считаются одним контактом, и ключ, который принимает каждый метод контактов, поскольку идентификатор контакта наружу не выдаётся. Строки принадлежат рабочему пространству, а не участнику или ключу, который их записал, так что все участники и все ключи рабочего пространства читают и пишут одну адресную книгу.
namestring or null- Отображаемое имя или null, если для адреса никогда не записывалось имя. Автоматическая запись несёт имя, только когда заголовок содержал что-то кроме самого адреса, и она никогда не может перезаписать имя, которое ввёл пользователь.
sourcestring- `auto` означает, что строка записана, потому что пользователь отправил письмо на этот адрес. `manual` означает, что кто-то ввёл его вручную, а это существенно иное утверждение, и upsert никогда не понижает `manual` обратно до `auto`. Почта, приходящая с адреса, намеренно не записывает никакой строки, поэтому того, кто только писал вам, здесь нет. Считайте значение открытой строкой, потому что столбец является произвольным текстом со значением по умолчанию `manual`.
notesstring or null- Произвольный текст, который кто-то написал об этом человеке в приложении или через `update`, никогда не генерируется. null, если никто ничего не написал, а `'notes' => null` в `update` его очищает.
lastSeenAtstring or null- Строка ISO 8601 в UTC, которая обновляется каждый раз, когда участник отправляет на этот адрес письмо из композера приложения, а не когда с него приходит почта (это ничего не записывает). null у контакта, сохранённого через `create`, которому никогда не писали, и такие контакты идут последними в порядке убывания `lastSeenAt`, в котором отвечает этот маршрут.
audiencesarray- Только в `get`, `create`, `update`, `save` и `setAudiences`, никогда в строках списка. Все аудитории, в которых состоит контакт, включая аудиторию по умолчанию, в виде массива с `id`, `name` и `builtin`. `builtin` равен `default` у аудитории, к которой принадлежит каждый контакт, и null у созданной кем-то, поэтому ветвитесь по нему, а не по имени, которое может изменить кто угодно.
photoUrlstring or null- Где отдаётся фото контакта, или null, если его нет. `setPhoto` его задаёт, и каждая загрузка получает новый URL.
Задание аудиторий контакта
setAudiences($email, ['audienceIds' => [...]]) одним запросом точно задаёт, в каких аудиториях состоит один контакт. Контакт вступает во все перечисленные аудитории, в которых ещё не состоит, и выходит из всех остальных, а вызов возвращает контакт после изменения вместе с его audiences. Ему нужна audiences:write, потому что он записывает членство, а не сам контакт, и повтор ничего не меняет, поэтому клиент повторяет его после сетевого сбоя.
Аудитория по умолчанию сохраняется всегда, поэтому 'audienceIds' => [] оставляет контакт только в аудитории по умолчанию. Принимает до 100 идентификаторов. Идентификатор, который не называет ни одной аудитории этого рабочего пространства, даёт 404 audience_not_found, и ничего не меняется, а адрес, который не является контактом, даёт 404 contact_not_found. Оба выбрасывают NotFoundException.
Все со страницы «Контакты»
listPeople перечисляет людей, которых показывает страница «Контакты» в приложении: сохранённые контакты и каждый адрес из почты, у каждого saved, threads и lastAt, и возвращает OpenEmail\Result\PeoplePage, которая добавляет seen к items, hasMore и nextCursor. list возвращает только сохранённые контакты. Адреса из почты приходят, только если у ключа есть ещё и threads:read, а $page->seen сообщает, пришли ли они. sort: принимает recent, name или threads, и OpenEmail\Constants\PeopleSorts их перечисляет. q: ищет по именам, адресам и заметкам, а blocked: true оставляет людей, которых блокирует список блокировки рабочего пространства, включая правила на целый домен. blockedBy называет правило в каждой строке.
use OpenEmail\Constants\PeopleSorts; $page = $client->contacts->listPeople(sort: PeopleSorts::THREADS, limit: 50); foreach ($page as $person) { if (!$person['saved'] && $person['threads'] > 5) { $client->contacts->save($person['email']); }} $blocked = $client->contacts->listAllPeople(blocked: true);echo $page->seen ? 'saved and seen' : 'saved only', ', ', count($blocked), ' blocked', PHP_EOL;listAllPeople возвращает все страницы одним массивом, а iteratePeople возвращает Generator, который выдаёт каждого человека. Ни один из них не сообщает seen, поэтому, чтобы узнать его, прочитайте одну страницу через listPeople. Курсор непрозрачен, поэтому передавайте nextCursor обратно как cursor: ровно в том виде, в каком он пришёл, с теми же sort:, q: и blocked:.
Сохранение, удаление и фото
save($email) с необязательным массивом из name и notes соответствует действиям «Добавить в контакты» и «Оставить в контактах»: сохраняет адрес, который ещё не является контактом, оставляет записанный при отправке как сохранённый вручную и возвращает удалённый. delete соответствует действию «Удалить»: убирает сохранённый контакт и скрывает адрес, чтобы композер не записал его снова, и принимает также адрес, который встречался только в почте. wasSaved в возвращаемом массиве сообщает, какой это был случай. deleteMany удаляет до 200 за один вызов.
$client->contacts->save('[email protected]', ['name' => 'Grace Hopper']); $contact = $client->contacts->setPhoto('[email protected]', file_get_contents('photo.jpg'), contentType: 'image/jpeg');echo $contact['photoUrl'], PHP_EOL; $client->contacts->setPhoto('[email protected]', new \SplFileInfo('avatar.png')); $client->contacts->removePhoto('[email protected]');$client->contacts->deleteMany(['[email protected]', '[email protected]']);setPhoto отправляет байты изображения как есть: PNG, JPEG, WebP или GIF до 5 МБ, вписанные в квадрат 512 пикселей. Байты передаются как строка, ресурс потока из fopen, SplFileInfo либо поток PSR-7 или загруженный файл. Передайте contentType: или байты, которые несут собственный тип: загруженный файл PSR-7 или загрузку Symfony либо Laravel с её медиатипом, или файл либо поток, имя которого заканчивается на .png, .jpg, .jpeg, .webp или .gif. Без типа байты уходят как application/octet-stream, и сервер отклоняет их с 422 invalid_image. OpenEmail\Constants\ContactPhotoTypes перечисляет четыре типа. Адрес сначала должен стать сохранённым контактом.
Блокировка
block($email) вносит адрес в список блокировки рабочего пространства, чтобы письма с него отклонялись, отбрасывая плюс-метку, а unblock($email) снимает каждое правило, которое его блокирует. Обоим нужен settings:write, потому что они меняют список блокировки, а не контакт, и ни одному не нужно, чтобы адрес был контактом.
Когда unblock снимает правило на целый домен, removed перечисляет его с list, равным blockedDomains, и вместе с ним разблокируются все адреса этого домена. OpenEmail\Constants\ContactBlockLists называет оба списка.
Переписка и активность
listThreads($email) постранично перебирает цепочки во всех папках, в которых этот адрес писал или ему писали, а listAllThreads и iterateThreads обходят их. activity($email) возвращает числа, стоящие за вкладкой «Активность» контакта: полученные и отправленные по интервалам, цепочки, ждущие вашего ответа, и медианное время ответа в каждую сторону. Обоим нужна threads:read.
$threads = $client->contacts->listThreads('[email protected]', q: 'invoice'); $activity = $client->contacts->activity( '[email protected]', minutes: 30 * 24 * 60, grain: 'day', offsetMinutes: intdiv((int) date('Z'), 60),); echo count($threads), ' threads, ', $activity['totals']['waiting'], ' waiting on you', PHP_EOL;activity принимает именованные аргументы. minutes: задаёт окно, которое без него составляет 90 дней. grain: задаёт ширину интервала: minute, hour или day. offsetMinutes: задаёт, на сколько минут к востоку от UTC проходит граница суток, а intdiv((int) date('Z'), 60) даёт смещение часового пояса, настроенного в PHP.