Skip to the documentation
Python

openemail.labels

Every method in this namespace: its signature, its parameters, what it returns and an example.

Methods

The labels a thread can carry.

labels.list()

List the workspace's labels, a page at a time

Scopeslabels:readPages through results
Signature
def list(    *,    limit: int | None = None,    cursor: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Page[LabelResource]

Returns one page of the workspace's user labels, sorted by name and then by id. list_all collects every page into one list and iterate walks them lazily. Each label carries its colour, threadCount (how many conversations carry it now) and createdAt and updatedAt, which is everything the Labels table in the app shows.

System labels such as INBOX, STARRED and UNREAD are not listed. A thread carries them and threads.update takes them, but they cannot be renamed, recoloured or deleted.

color is None on a label saved with no colour. Otherwise color['backgroundColor'] is a hex value or a gradient token such as gradient:sunset, and color['textColor'] is the ink the app draws on it, worked out on the server rather than stored.

Parameters

limitint

Page size, from 1 to 100. The server defaults to 25.

cursorstr

The nextCursor of the previous page, passed back as it came. Leave it out for the first page.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Page[LabelResource], a dict with items, hasMore and nextCursor. Each item has id, name, type, color, threadCount, createdAt and updatedAt.

Example

from openemail import openemail page = openemail.labels.list(limit=50) for label in page['items']:    print(label['name'], label['threadCount']) if page['hasMore']:    print('more after', page['nextCursor'])

Notes

  • Labels belong to the workspace, so a narrowed key still sees every label. threadCount is the exception: it counts only conversations delivered to the addresses the key holds.

  • An id is fixed when the label is created and survives a rename, so match on id rather than name in stored configuration.

  • The sidebar order in the app is each person's own arrangement and is not exposed.

  • The cursor is opaque and holds where the last row sat in this order, so a row deleted or edited between pages never breaks the walk: the next page starts at the first row that sorts after it. A cursor this list did not hand out is a 400 invalid_cursor.

Also available in

API
GET /labels
TypeScript
labels.list()
Ruby
labels.list
PHP
labels->list
Go
Labels.List
Java
labels().list
C#
Labels.ListAsync
CLI
openemail labels list

labels.list_all()

Collect every label into one list

Scopeslabels:readPages through results
Signature
def list_all(    *,    limit: int | None = None,    cursor: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> builtins.list[LabelResource]

Walks every page of list and returns all labels in one list, sorted by name and then by id. One request per page.

Parameters

limitint

Page size for each request, from 1 to 100. The server defaults to 25.

cursorstr

Starts the walk after this cursor instead of the first page.

api_keystr

Overrides the client API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

list[LabelResource] holding every label.

Example

from openemail import openemail labels = openemail.labels.list_all(limit=100)ids_by_name = {label['name']: label['id'] for label in labels} receipts = ids_by_name.get('Receipts') if receipts is not None:    openemail.threads.update('thr_8f2c41d0a3b94e6f', {'addLabelIds': [receipts]})

Notes

  • If any page fails, the call raises and the labels already fetched are discarded.

Also available in

API
GET /labels
TypeScript
labels.listAll()
Ruby
labels.list_all
PHP
labels->listAll
Go
Labels.ListAll
Java
labels().listAll
C#
Labels.ListAllAsync

labels.iterate()

Stream the labels one at a time

Scopeslabels:readPages through results
Signature
def iterate(    *,    limit: int | None = None,    cursor: str | None = None,    api_key: str | None = None,    timeout: float | None = None,) -> Iterator[LabelResource]

Returns a generator that yields labels one at a time, sorted by name and then by id, and requests the next page only once the current one is used up. Nothing is fetched until the loop starts, and breaking out of it stops the requests.

Parameters

limitint

Page size for each request, from 1 to 100. The server defaults to 25.

cursorstr

Starts the walk after this cursor instead of the first page.

api_keystr

Overrides the client API key for every page of this walk.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

Iterator[LabelResource], a generator yielding one label per step.

Example

from openemail import openemail for label in openemail.labels.iterate():    if label['threadCount'] == 0:        print('unused', label['name'])

Notes

  • The generator is lazy, so an abandoned loop costs only the pages you consumed.

Also available in

API
GET /labels
TypeScript
labels.iterate()
Ruby
labels.iterate
PHP
labels->iterate
Go
Labels.Iterate
Java
labels().iterate
C#
Labels.IterateAsync

labels.list_colors()

List the colours the app offers for labels

Scopeslabels:read
Signature
def list_colors(    *,    api_key: str | None = None,    timeout: float | None = None,) -> builtins.list[LabelColorResource]

Returns the whole label palette as a plain list: the fourteen solid colours and seven gradients the app offers when you make or edit a label, in the order it shows them. It is a fixed catalogue, so there is no paging. Fetch it once and keep it.

value is what to send as color['backgroundColor']: a hex such as #3B82F6 for a solid, a token such as gradient:sunset for a gradient. textColor is the ink drawn on it. A gradient also carries from and to, drawn at 135 degrees, and solid, one hex for places a gradient cannot go.

A label may carry a colour outside this list. Any hex set through the API is kept as it is, and the app offers it back as its own swatch.

Parameters

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

list[LabelColorResource], each with kind, name, value, solid, from, to and textColor.

Example

from openemail import openemail colors = openemail.labels.list_colors()sunset = next((color for color in colors if color['name'] == 'sunset'), None) label = openemail.labels.create(    {'name': 'Launch', 'color': {'backgroundColor': sunset['value'] if sunset else '#EF4444'}})print(label['id'], label['color'])

Notes

  • kind is solid or gradient. from and to are None on a solid.

Also available in

API
GET /labels/colors
TypeScript
labels.listColors()
Ruby
labels.list_colors
PHP
labels->listColors
Go
Labels.ListColors
Java
labels().listColors
C#
Labels.ListColorsAsync
CLI
openemail labels list-colors

labels.get()

Read one user label by id

Scopeslabels:read
Signature
def get(    id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> LabelResource

Looks up a single user label and returns it in the same shape as a row of list, with its colour, threadCount, createdAt and updatedAt.

Only user labels are served. INBOX, TRASH and the other system ids are a 404 here even though threads carry them. Ids are matched exactly, so user_receipts does not find USER_RECEIPTS.

Parameters

idstrRequired

Label id such as USER_RECEIPTS, matched case sensitively.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

LabelResource with id, name, type (always user), color, threadCount, createdAt and updatedAt.

Example

from openemail import openemail label = openemail.labels.get('USER_RECEIPTS')color = label['color'] print(label['name'], color['backgroundColor'] if color else 'no colour', label['threadCount'])

Notes

  • A missing label raises OpenEmailApiError with is_not_found set to True and code resource_not_found.

Also available in

API
GET /labels/{id}
TypeScript
labels.get()
Ruby
labels.get
PHP
labels->get
Go
Labels.Get
Java
labels().get
C#
Labels.GetAsync
CLI
openemail labels get

labels.create()

Create a user label

Scopeslabels:write
Signature
def create(    body: LabelInput,    *,    api_key: str | None = None,    timeout: float | None = None,) -> LabelResource

Creates a label and returns it as it is stored. name is trimmed and must then be 1 to 225 characters. The id is derived from the name as USER_ followed by the name upper cased, with each run of whitespace turned into _, so Big Clients becomes USER_BIG_CLIENTS, and it never changes afterwards.

A name another label already has, compared without case, is refused with 409 label_name_taken, and so is a name whose id another label holds because it was created under that name and renamed since. An existing label is never silently overwritten. A workspace holds at most 50 user labels, and the call past that is a 422 label_limit_reached on name.

color is optional. color['backgroundColor'] is a hex colour (#RGB, #RGBA, #RRGGBB or #RRGGBBAA, stored upper cased) or a gradient token such as gradient:sunset, and anything else is a 422 invalid_parameter with param set to color.backgroundColor. list_colors returns the palette the app offers. textColor may be sent but is ignored: the ink is worked out from the background. Leaving color out stores no colour.

aiInstructions says what the label is for in plain words, such as invoices and receipts. With aiEnabled on, AI reads each arriving message against them and adds the label when it fits. It skips spam, the Bin, encrypted mail and muted threads, and it only labels: it never archives or moves anything. Turning aiEnabled on needs instructions and a paid plan, and a workspace may have 10 such labels. Without instructions it is a 422 invalid_parameter on aiInstructions, on the free plan a 403 plan_required, and past the tenth a 422 ai_label_limit_reached.

Parameters

body['name']strRequired

Display name, trimmed, 1 to 225 characters. Also decides the id.

body['color']LabelColorInput

The colour. Leave it out for a label with no colour.

body['color']['backgroundColor']strRequired

A hex colour such as #3B82F6 or a gradient token such as gradient:sunset, at most 32 characters. Required once color is given.

body['color']['textColor']str

Accepted and ignored. The ink is worked out from backgroundColor.

body['aiInstructions']str | None

What the label is for, in plain words, up to 500 characters, such as invoices and receipts.

body['aiEnabled']bool

Have AI apply the label to arriving mail that fits aiInstructions. It needs instructions and a paid plan, and a workspace may have 10 such labels.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

LabelResource with the new id, the trimmed name, color, threadCount of 0, aiInstructions, aiEnabled, createdAt and updatedAt.

Example

from openemail import openemail label = openemail.labels.create(    {'name': 'Big Clients', 'color': {'backgroundColor': 'gradient:aurora'}})color = label['color'] print(label['id'], color['textColor'] if color else None)

Notes

  • The SDK does not retry a create after a network failure. If you retry by hand after a lost response, a 409 label_name_taken means the first attempt succeeded.

  • The label is created before its AI settings are saved, so a create refused with plan_required, ai_label_limit_reached or invalid_parameter on aiInstructions leaves the label in place with no instructions and aiEnabled off. Set them with update rather than creating it again.

  • test runs the instructions over recent mail and shows what they would catch, without labelling anything.

Also available in

API
POST /labels
TypeScript
labels.create()
Ruby
labels.create
PHP
labels->create
Go
Labels.Create
Java
labels().create
C#
Labels.CreateAsync
CLI
openemail labels create

labels.update()

Rename or recolour a user label

Scopeslabels:write
Signature
def update(    id: str,    patch: LabelPatch,    *,    api_key: str | None = None,    timeout: float | None = None,) -> LabelResource

Renames a label, recolours it, or changes what AI does with it, and returns it as it is stored. Send any of name, color, aiInstructions and aiEnabled: a key left out stays as it is, and a patch with none of them is a 422. {'color': None}, or an empty backgroundColor, clears the colour.

The id never changes. A label created as Receipts keeps USER_RECEIPTS after a rename to Invoices, and every thread keeps the label. A new name another label already has, compared without case, is refused with 409 label_name_taken.

Colours follow the rules on create: a hex value or a gradient token, and anything else is a 422 invalid_parameter. Only user labels can be changed, and a system label id is a 404.

With aiEnabled on, AI reads each arriving message against aiInstructions and adds the label when it fits. It skips spam, the Bin, encrypted mail and muted threads, and it only labels: it never archives or moves anything. Turning it on needs instructions and a paid plan, and a workspace may have 10 such labels. Without instructions it is a 422 invalid_parameter on aiInstructions, on the free plan a 403 plan_required, and past the tenth a 422 ai_label_limit_reached. {'aiInstructions': None} clears the instructions, which also turns aiEnabled off.

Parameters

idstrRequired

Label id such as USER_RECEIPTS.

patch['name']str

New display name, trimmed, 1 to 225 characters. Left out, the name stays.

patch['color']LabelColorInput | None

New colour. None clears it, and leaving it out keeps the stored colour.

patch['color']['backgroundColor']strRequired

A hex colour or a gradient token, at most 32 characters. An empty string clears the colour.

patch['color']['textColor']str

Accepted and ignored. The ink is worked out from backgroundColor.

patch['aiInstructions']str | None

What the label is for, in plain words, up to 500 characters. None clears it, which also turns aiEnabled off. Left out, it stays.

patch['aiEnabled']bool

Whether AI applies the label to arriving mail that fits aiInstructions. Turning it on needs instructions and a paid plan, and a workspace may have 10 such labels. Left out, it stays.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

LabelResource with the unchanged id and the new name, color, aiInstructions and aiEnabled.

Example

from openemail import openemail saved = openemail.labels.update('USER_RECEIPTS', {'color': {'backgroundColor': '#EA9602'}})color = saved['color'] print(saved['id'], saved['name'], color['backgroundColor'] if color else None)

Notes

  • The SDK retries this call after a network failure, since the same patch sent twice leaves the same label.

  • test runs instructions over recent mail and shows what they would catch, so you can try wording before you save it here.

Also available in

API
PATCH /labels/{id}
TypeScript
labels.update()
Ruby
labels.update
PHP
labels->update
Go
Labels.Update
Java
labels().update
C#
Labels.UpdateAsync
CLI
openemail labels update

labels.test()

Try a label on recent mail

Scopeslabels:writethreads:read
Signature
def test(    id: str,    body: LabelTest | None = None,    *,    api_key: str | None = None,    timeout: float | None = None,) -> LabelTestResource

Runs the AI over the newest 20 inbox threads and returns the ones the label would catch, without labelling anything. Send aiInstructions to try wording you have not saved yet, or leave body out to try what the label holds.

It spends one AI action of the caller, which the labelling of arriving mail never does. A message that arrived encrypted is skipped, and a key limited to particular addresses reads only the threads delivered to them.

Parameters

idstrRequired

Label id such as USER_RECEIPTS, as list returns it.

body['aiInstructions']str

What the label is for, in plain words, 1 to 500 characters. Left out, the instructions saved on the label are tried.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

LabelTestResource with object set to label_test, labelId, examined and matched. examined is how many recent threads were read, and each match has threadId, subject, sender and receivedOn, with sender the address the newest message is from and receivedOn when it arrived, as its Date header gives it, or None.

Example

from openemail import openemail result = openemail.labels.test('USER_RECEIPTS', {'aiInstructions': 'invoices and receipts'}) print(f'{len(result["matched"])} of {result["examined"]} would get the label') for match in result['matched']:    print(match['sender'], match['subject'])

Notes

  • Needs both labels:write and threads:read. An unknown label id is a 404.

  • With no instructions to try, neither in the call nor on the label, it is a 422 invalid_parameter. A spent AI allowance is a 429 ai_quota_exceeded, and a 409 ai_not_configured while AI is unavailable.

  • Nothing is saved: the label keeps the instructions it had. Save the wording with update once it catches what you want.

  • The SDK does not retry it, because each call spends an AI action.

Also available in

API
POST /labels/{id}/test
TypeScript
labels.test()
Ruby
labels.test
PHP
labels->test
Go
Labels.Test
Java
labels().test
C#
Labels.TestAsync
CLI
openemail labels test

labels.delete()

Delete a user label and remove it from every thread

Scopeslabels:write
Signature
def delete(    id: str,    *,    api_key: str | None = None,    timeout: float | None = None,) -> DeletedLabelResource

Deletes a user label and, in the same transaction, takes it off every thread that carried it. The threads are otherwise untouched, so a thread that was only filed under this label stays in whatever folder it was in.

There is no undo. Creating a label with the same name again produces the same id, but the threads it was removed from do not get it back. Only user labels can be deleted, and a system label id is a 404.

Parameters

idstrRequired

Label id such as USER_OLD_PROJECT.

api_keystr

Overrides the client API key for this call only.

timeoutfloat

Seconds this call may take, the response included, before it raises OpenEmailNetworkError with is_timeout. It overrides the client's timeout for this call, and 0 turns the limit off.

Returns

DeletedLabelResource with object set to label, the id, and deleted set to True.

Example

from openemail import openemail removed = openemail.labels.delete('USER_OLD_PROJECT') print(removed['id'], removed['deleted'])

Notes

  • The SDK does not retry a delete. A 404 on your own second attempt after a lost response means the first one worked.

  • get first if you want to say how many conversations will lose the label: its threadCount is that number.

Also available in

API
DELETE /labels/{id}
TypeScript
labels.delete()
Ruby
labels.delete
PHP
labels->delete
Go
Labels.Delete
Java
labels().delete
C#
Labels.DeleteAsync
CLI
openemail labels delete