Skip to content

Releases

0.7.0

Choose a tag to compare

@BeneODev BeneODev released this 10 Nov 13:16
24ff25c

Breaking changes

This release changes how the MCP server starts in one case. It is described in full under Changed below.

  • MCP server over HTTP: on an address other hosts reach, such as 0.0.0.0 in its image, the server no longer starts without MAILBOX_MCP_BEARER_TOKEN and MAILBOX_MCP_ALLOWED_HOSTS or MAILBOX_MCP_ALLOWED_ORIGINS. Set them before you update the image. On 127.0.0.1 and over stdio nothing changes.

The database schema stays the same, no migration runs at the first start.

Added

  • MAILBOX_MCP_CATALOGUE=true starts the MCP server without a service
    or a token: it lists every tool and refuses every call but
    server_status. It is meant for directories that read the tool list
    in a sandbox. Beside MAILBOX_SERVICE_TOKEN the server does not
    start. Without the setting nothing changes.
  • benethos-mailbox-mcp --list-tools prints every tool of the MCP server
    as a client lists it, as JSON, without a service or a token.

Changed

  • Breaking: the MCP server over HTTP on an address other hosts
    reach, such as 0.0.0.0 in its image, no longer starts without
    MAILBOX_MCP_BEARER_TOKEN and MAILBOX_MCP_ALLOWED_HOSTS or
    MAILBOX_MCP_ALLOWED_ORIGINS. Before, it started with a warning and
    admitted anyone who reached the port. On 127.0.0.1 and over stdio
    nothing changes.
  • The MCP tools describe every parameter, the ids by the tool that
    answers them. Each tool says when to use it rather than a related
    one, whether it changes anything, and what it answers. What the tools
    do is unchanged.
  • The images install fastapi 0.162.2, pypdfium2 5.15.2 and
    publicsuffixlist 1.1.2.20261109 from the lockfile. pypdfium2 brings
    PDFium 8149 and fixes a rare error when its objects are closed while
    the garbage collector runs or the interpreter shuts down.

Fixed

  • On Python 3.11 and 3.12 the descriptions of the MCP tools carried the
    indentation of their source code, spaces a client sent to the model
    with every description. They are now the same on every Python version.
  • A page of the UI given a return address whose host cannot be read,
    such as next=//[, answered with a plain error 500. It now goes to
    the page's own default, as for any address outside the UI.
  • A cursor of a Microsoft account's message list that names a host
    which cannot be read answered with an error 500 outside the error
    envelope. It is now 400 with the code bad_request, as any other
    forged cursor.
  • The client raised httpx's own error for an answer it could not read,
    such as a body that is not what its Content-Encoding says. It is now
    an ApiError with the code unexpected_response. The MCP server,
    which asks the service what its token may do while it runs, ended on
    such an answer. It now keeps its tools and asks again.
  • The MCP tool get_thread said the bodies together are cut at
    max_chars. Each mail keeps at least 200 characters, so twenty mails
    can bring more. The description now says so. What the tool does is
    unchanged.

0.6.1

Choose a tag to compare

@BeneODev BeneODev released this 07 Nov 13:41
bd5ffd9

Changed

  • The images install anyio 4.16.0, python-multipart 0.0.33, platformdirs
    4.14.5, typing-inspection 0.4.5, pydantic 2.14.3, publicsuffixlist
    1.1.2.20261107, fastapi 0.162.1, click 8.5.1, sse-starlette 3.7.0 and
    pypdfium2 5.15.0 from the lockfile. The PDFium of pypdfium2 5.15.0 has
    fixes for known vulnerabilities.
  • The images build with uv 0.13.7, on the build of python:3.15-slim from
    2026-11-05, still Python 3.15.2.
  • benethos-mailbox-service needs dnspython 2.9.2 or newer, the first
    version without a known vulnerability. The images install it from the
    lockfile. The advisory concerns zone files, and the service reads none.
  • The images name their user by its id, USER 10001 instead of
    USER mailbox, so that a host or an orchestrator can see it is not root.
    It is the same user.
  • The UI ships htmx 4.0.1 instead of 2.0.4. Only the page of a sign-in with a
    code uses it, and that page works as before.

Fixed

  • The UI's next and back values no longer lead out of /ui with a
    .. in the path, also written as %2e%2e. Such a value lands on the
    fallback page. It could not reach another host before either.

0.6.0

Choose a tag to compare

@BeneODev BeneODev released this 29 Oct 20:41
21530f5

Breaking changes

This release changes the contract in one place. It is described in full under Changed below.

  • Keywords: a replacement list keywords in PATCH /v1/accounts/{account_id}/messages/{message_id} and the batches keeps the marks a message has, such as $answered. Name a mark in remove_keywords to take it off.

The database schema stays the same, no migration runs at the first start.

Added

  • benethos-mailbox-mcp: the tool server_status, always offered. It
    names the server's version, the transport, Python, the MCP library
    and the uptime. With details it adds the service as the server last
    saw it: whether it answered and how long ago, its version and whether
    it matches, whether it took the token, and the tools offered. The
    server asks /health for the service's version as it starts and each
    time it asks what its token may do.
  • benethos-mailbox-client: Folder.subscribed, whether the folder is
    subscribed on the server, as the API says.
  • benethos-mailbox-mcp: list_accounts names three more things an
    account may lack in unsupported: a mail in several folders,
    conversations and a copy in the sent folder.
  • benethos-mailbox-mcp: send_message and send_draft answer
    sent_copy_id, the copy in the sent folder where the account keeps
    one.
  • benethos-mailbox-mcp: list_accounts names per account the marks
    it writes in marks_written. A mark outside them is refused.
  • benethos-mailbox-mcp: search_messages and whats_new take
    account_ids, several accounts in one call, beside account_id.
  • benethos-mailbox-mcp: share_attachment takes expires_in, a
    link that lives fewer seconds than the service's default.
  • benethos-mailbox-mcp: each hit of search_messages carries the
    first 120 characters of its text as snippet. get_message marks an
    inline attachment, such as a logo in a signature, as (inline).
  • benethos-mailbox-client: ApiError.retry_after, the seconds the
    Retry-After header of a limit asks to wait, else None.
  • benethos-mailbox-client: Sent.sent_copy_id, the copy in the sent
    folder where the service put one. Sent.message_id_header is a
    string, as the API always answers one.
  • benethos-mailbox-client: Failed.code, the code of the error a
    batch answered for an id, and Page.incomplete, the accounts that did
    not answer as records NotAnswering with their code. not_answering
    stays as it was.
  • benethos-mailbox-client: list_changes takes since and limit
    as optional, as the API does, and list_messages, list_drafts and
    list_threads their limit.

Changed

  • Breaking: a replacement list keywords in
    PATCH /v1/accounts/{account_id}/messages/{message_id} and the
    batches keeps the marks a message has, such as $answered. It took
    them off on IMAP and JMAP, but not on Microsoft. Name a mark in
    remove_keywords to take it off.
  • GET /v1/messages reads at most ten pages of an account for one page
    of the list. Where the grants reach little of the folder, a page may
    hold fewer messages than limit, or none, with a next_cursor to
    read on. One request read the whole account before.
  • The help of keys import, backup verify --recovery-key and
    restore --recovery-key says the recovery key is asked for at a
    hidden prompt, and read from stdin only when piped.
  • The service runs on FastAPI 0.162 and Starlette 1.10.
  • Both images run on Python 3.15.2. It rejects TLS certificate chains
    with the compromised eMudhra intermediate CA keys.
  • The images install pydantic 2.14.2, platformdirs 4.14.0 and dnspython
    2.9.1 from the lockfile.
  • benethos-mailbox-service needs dnspython 2.9.0 or newer, the first
    version without a known vulnerability. An install from the lockfile,
    as the images do, had it already.
  • POST /v1/users/{user_id}/tokens refuses an expires_at more than
    3650 days, ten years, ahead with 400, as the UI did. A token without
    an end stays possible.
  • benethos-mailbox-client: the repr of Environment leaves the token
    out.
  • benethos-mailbox-mcp: the filters text, sender, to and
    subject of search_messages take one line of 1 to 200 characters,
    as the API does, and refuse other values in their own names. The
    name of create_folder and update_folder takes at most 200.
  • benethos-mailbox-mcp: the log names why a tool failed when it was
    no refused argument: a PDF that took too long, an attachment too large
    to hand over, a mail without a thread.
  • benethos-mailbox-client: the content_type of an AttachmentLink
    is a media type, else application/octet-stream, as for the bytes of
    an attachment. The sender of the mail chose it.
  • benethos-mailbox-client and benethos-mailbox-mcp refuse a
    MAILBOX_SERVICE_URL with a user or password in it. The address is
    named in the error when the service cannot be reached, which reaches
    the model.
  • benethos-mailbox-mcp: share_attachment names the content type
    inside the marker, beside the file name, since the sender chose it.
  • The account page of the UI says what the account can do and which
    marks it writes.
  • A sign-in with a code in the UI answers a page of its own, so a reload
    starts no second sign-in at the provider. The batch of the Users list
    and Set password for another user ask first.

Fixed

  • benethos-mailbox-mcp: get_message shows bcc and reply_to
    where a mail has them. A draft replaced with update_draft after
    reading it lost both.
  • A list of messages or threads in a folder the account does not have
    answers 404 on every provider. It reached the provider first, and
    Microsoft answered 400.
  • A message id that Microsoft cannot read answers 404 when the message
    is read, fetched raw, deleted or its keywords changed, as on every
    other provider. Graph answered 400.

Security

  • A folder name with a line break or NUL never reaches a command of an
    IMAP server. A folder id is not signed:
    a caller with mail.read could build one whose name ended SELECT
    and started a command of their choosing, run with the account's
    credentials. Python 3.14 and newer refused it already, 3.11 to 3.13
    sent it. The images run on 3.15 and were not affected.
  • An IMAP folder id, message id or cursor whose folder name holds a
    control character answers like one of the wrong form: 404 for an
    id, 400 for a cursor. No server has such a folder.

Full changelog: v0.5.2...v0.6.0

0.5.2

Choose a tag to compare

@BeneODev BeneODev released this 22 Oct 19:55
99c4388

Changed

  • The images build on Python 3.15.1 and uv 0.13.5.
  • The service runs on FastAPI 0.161.
  • The icons of the UI come from Bootstrap Icons 1.13.2.

Full changelog: v0.5.1...v0.5.2

0.5.1

Choose a tag to compare

@BeneODev BeneODev released this 19 Oct 22:34
5b1c2c0

Changed

  • The images take the tag latest from a full release only, not from a
    pre-release, and edge from main only.
  • The service runs on FastAPI 0.149 and Starlette 1.9.
  • The images build on python:3.15-slim and uv 0.13.2, each pinned
    by its digest.

Full changelog: v0.5.0...v0.5.1

0.5.0

Choose a tag to compare

@BeneODev BeneODev released this 17 Oct 07:11
26c5285

Breaking changes

This release changes the contract in these places. Each is described in full under Changed below.

  • Errors: what an account lacks answers 501 not_supported on every account, before the provider is asked. It answered 409, 400 or 404 before, and a role without a folder answered 404 or 409.
  • Errors: a provider's 403 answers provider_error on every account, a JMAP server's too, also for a JMAP method error forbidden. JMAP's 400 and 409 answer bad_request and conflict.
  • Accounts: PATCH /v1/accounts/{account_id} with settings that name a new server needs every credential of the account in the same request.
  • Accounts: the capabilities are finer, one per thing they guard. flags is gone, stable_ids, delta and server_search left the API. A client that checks the values strictly fails on the new ones.
  • Accounts: operations in GET /v1/me leaves out what the account cannot do.
  • Messages: date is the Date header on every account, and the new field received is when the account received the mail. Lists sort, filter and merge by received.
  • Lists: a list without a folder is the inbox on every account. The role all lists every folder except the trash and junk. A cursor of a list without a folder handed out before is refused once.
  • Keywords: keywords keep the provider's spelling and compare without case. Notes a server keeps for itself, such as $hasattachment, leave keywords.

The database moves from schema 19 to 22 at the first start. Make a backup before the update: a version before 0.5.0 cannot open the database afterwards.

Added

  • The MCP server asks what its token may do every
    MAILBOX_MCP_RIGHTS_SECONDS, 60 by default, and when a tool is
    refused with 403 forbidden, a right missing, at most every five
    seconds. With 0 it asks only at start.
    When its tools change it tells its clients with
    notifications/tools/list_changed and announces tools.listChanged,
    so a right granted or taken away shows without a restart. When the
    service refuses the token, every tool goes away, also when a tool is
    refused with 401. A user that may do nothing keeps list_accounts
    alone, which lists no account. A refused token is asked
    for no more, since each refusal counts against the address, and a new
    one needs a restart.
  • POST /v1/accounts/{account_id}/messages/{message_id}/attachments/{attachment_id}/links
    (share_attachment) makes a link to an attachment for another
    service, which fetches the file itself with GET /v1/links/{secret}:
    once, without a token, until expires_at. A link holds
    MAILBOX_SERVICE_LINK_SECONDS, 120 by default, and expires_in asks
    for fewer. It needs an API token and the new group mail.share, which
    is in no role template. When the link is used, its token must still
    act and hold get_attachment and share_attachment on the message.
    Every refusal answers the same 404 and counts against the client
    address as a wrong token does. The audit keeps mailbox.link_shared
    and mailbox.link_used, never the secret or the URL. When the mail
    server fails to
    hand out the file, the link holds for the rest of its time, the answer
    is 502 provider_unavailable to try again, the audit says
    mailbox.link_returned, and the address that asked counts no
    failure. Whoever holds the URL fetches the
    file. The service logs its path masked, but a reverse proxy in front
    writes the whole URL into its access log, where it opens the file
    until the link is used or ends. The Python client has
    share_attachment, the MCP server a tool of the same name. The tool
    is not marked read-only, and list_accounts names share as a kind
    of its own. The database moves to schema 22.
  • An account answers operations, in GET /v1/accounts/{id}, the list
    and GET /v1/me: the operations the caller can run there, its rights
    and what the account can do together, by their names in the catalogue
    of rights. A client offers what is in it and needs no knowledge of
    providers. The Python client reads it as Account.operations.
  • The connect page of the UI offers one form per kind of account to
    set up by hand, IMAP, POP3 and JMAP, each with what to know of it.
    The forms follow from each kind's description of its connection.
  • The MCP server shows a mail's marks in words, such as answered,
    apart from its keywords, and its tools name no provider.
  • A send of a reply or a forward answers original_marked: whether the
    original carries $answered or $forwarded now. False where the
    account does not write that mark, as on Gmail, or the mark failed.
  • A Microsoft account reads and writes $answered and $forwarded, as
    the verb Outlook last carried out on a message, so Outlook shows a
    reply or a forward of the service too. One of the two at a time, as
    Outlook keeps it. Its marks name both. A change Exchange refuses as
    a conflict right after another one is tried again after a pause.
  • PATCH .../messages/{id} and a batch update take add_keywords and
    remove_keywords, which change the keywords a message has instead of
    replacing them, and add_folder_ids and remove_folder_ids, which
    put a message into folders and take it out of them where it can be in
    several, such as Gmail's labels. Each goes beside the list it changes,
    not with it. A message keeps one folder at least.
  • A draft and a message to send take attachments_from: attachments of
    other messages of the same account by message and attachment id,
    attached as they are. It needs the right to read that mail.
  • /v1/me says per account its status, whether it needs attention
    and when its last sync succeeded (last_sync_at).
  • An account and the accounts of /v1/me carry marks: the marks of
    RFC 8621, such as $answered or $junk, it reads (marks.read) and
    those it writes (marks.written). Setting or taking off a mark it
    does not write answers 501. The Python client has them as Marks.
  • Messages of IMAP accounts carry a thread_id. The service threads
    them itself, across folders, by Message-ID, In-Reply-To and
    References. Mail already known is threaded over the next sync
    passes. A message that ties two threads together merges the younger
    into the older, whose messages show as updated in the change feed.
    Every kind of account but POP3 has the capability threads: Gmail,
    JMAP and Microsoft keep the threads themselves.
    MAILBOX_SERVICE_THREAD_REFERENCES (20) sets how many of the
    References of a message count. The database moves to schema 20.
  • GET /v1/accounts/{account_id}/threads/{thread_id} (get_thread)
    answers a thread with its messages in every folder, oldest first. A
    longer thread lists its newest MAILBOX_SERVICE_THREAD_MESSAGES (500)
    and says truncated. GET /v1/accounts/{account_id}/threads
    (list_threads) answers the threads of a folder, each where its
    newest message in the folder is, with that message's subject, date
    and snippet. Both are in mail.read, and a grant
    that names folders sees the messages of a thread in them. POP3 answers
    501. The Python client has list_threads and get_thread.
  • The UI lists a folder by conversations where the account keeps
    threads, shows a conversation on a page of its own, and names the
    others of its conversation below a message.
  • The MCP server:
    • A new tool get_thread reads a conversation in every folder,
      oldest first, from a mail of it such as a search hit or from its
      thread id. The newest 20 mails come as plain text and share
      max_chars. Search results and get_message name the thread of
      a mail.
    • update_messages adds and takes off keywords and labels
      (add_folders, remove_folders, add_keywords,
      remove_keywords). At Gmail it labels a mail and leaves it in the
      inbox.
    • send_message, create_draft and update_draft take quote,
      forward_as, reply_to, attachments as base64 and
      attach_from, attachments of other mail of the account.
    • A new tool update_folder renames and moves a folder.
    • list_accounts names an account that needs a person, and why.
    • Search results and get_message name the folders and keywords of a
      mail. get_message names the mail a draft answers.

Changed

  • Breaking: a provider that refuses a request with 403 answers
    provider_error on every kind of account: the server took the
    credential and refused the request. A JMAP server's 403 answered
    provider_auth_failed and marked the account as needing a new
    sign-in. The same holds for a JMAP method error forbidden, as from
    a read-only token. A JMAP server's 400 and 409 answer
    bad_request and conflict, as Gmail's and Microsoft's do.
  • Breaking: PATCH /v1/accounts/{account_id} with settings that
    name a new server needs every credential of the account in the same
    request, else it answers 400. A server removed needs none. The UI
    says so under the password of an account.
  • benethos-mailbox-mcp requires mcp 2.3 and uvicorn 0.38 at least,
    the versions the workspace and the service already ask for.
  • Breaking: operations of an account in GET /v1/me leaves out
    what the account cannot do, such as list_threads on POP3. It named
    the caller's rights alone.
  • Breaking: the capabilities of an account are finer, each guarding
    one thing: read_state, stars, keywords, folders, move,
    trash, purge, labels, search, drafts, send, sent_copy,
    threads, push, external_changes and refused. flags is gone,
    and stable_ids, delta and server_search left the API. A client
    that checks the values strictly fails on the new ones.
  • Breaking: what an account lacks answers 501 not_supported, the
    same on every account, before the provider is asked: a send without
    an SMTP server, a draft without a drafts ...
Read more

0.4.1 (beta)

Choose a tag to compare

@BeneODev BeneODev released this 10 Oct 21:14
5aa8109

Added

  • From this release on, the MCP server is listed in the official MCP
    Registry as io.github.benethos-hub/benethos-mailbox-mcp, installed
    from PyPI with uvx or as the Claude Desktop extension below. It asks
    for MAILBOX_SERVICE_TOKEN, and takes MAILBOX_SERVICE_URL and
    MAILBOX_SERVICE_ALLOW_HTTP if set.
  • Each release carries the MCP server as a Claude Desktop extension,
    benethos-mailbox-mcp.mcpb. Claude Desktop installs it with uv and
    Python of its own, and asks for the token, the address of the service
    and whether plain http to another machine is allowed.

Changed

  • The package description of benethos-mailbox-mcp is one sentence of
    at most 100 characters, the same as in its registry entry.

0.4.0 (beta)

Choose a tag to compare

@BeneODev BeneODev released this 10 Oct 14:51
ea6d91f

Added

  • A second factor for the sign-in to the UI, a choice of each user. Its
    first method is an authenticator app (TOTP): a code after the
    password. A user holds it on up to ten named devices, each with codes
    of its own, added, renamed and removed on the page Second factor
    with a QR code. The first device brings ten recovery codes shown
    once, also as a text file to download. A further device, new
    recovery codes and the recovery key need a code. A password set by
    someone else is changed only after the code. docs/AUTHENTICATION.md
    has the details. Recovery codes made before schema 19 are void: make
    new ones on the page Second factor after the update.
  • Gmail and Google Workspace accounts over the Gmail API, with the
    kind of account gmail. They sign in with Google in the browser,
    through a Google client of the deployment's own, set up as
    docs/GOOGLE.md says and named in MAILBOX_SERVICE_OAUTH_GOOGLE_CLIENT_ID
    with its secret. Without one, Gmail connects over IMAP with an app
    password as before.
  • In a Gmail account labels are folders, and a message can be in
    several. "All Mail" is the folder ALL_MAIL with the role all.
    Setting keywords answers 501 not_supported. The change feed follows
    Gmail's history, asked every MAILBOX_SERVICE_SYNC_INTERVAL seconds.
  • Looking up a Gmail address offers Sign in with Google first,
    where the deployment has a Google client. The way over IMAP links to
    Google's page for app passwords.
  • Each user in /v1/users says whether it has a second factor for the
    UI sign-in, in second_factor. GET /v1/users/{user_id}/second-factor
    (get_second_factor, in users.read) lists the devices of each
    method, totp so far, never a secret. DELETE /v1/users/{user_id}/second-factor (remove_second_factor) removes the
    whole second factor, DELETE /v1/users/{user_id}/second-factor/totp/{device_id}
    (remove_totp_device) one TOTP device, both in users.manage, for a
    user whose rights the caller holds, never for the caller itself. The
    database moves to schema 19.
  • PATCH /v1/webhooks/{webhook_id} (update_webhook) changes a
    webhook's url, events or accounts. A field left out stays,
    accounts set to null is every account the owner may read. Its
    deliveries, where its posts stand and its secret stay. POST /v1/webhooks/{webhook_id}/secret (renew_webhook_secret) answers a
    new signing secret, shown once. The one before stops at once. Both are
    in webhooks.manage, for the webhook's owner alone, and audited as
    webhooks.changed and webhooks.secret_renewed. The UI's webhook
    page has a Change card and New secret. mailbox-client has both
    as update_webhook and renew_webhook_secret, with the records
    Webhook and WebhookSecret. The secret is a Secret, kept out of
    repr and str, read with get_secret_value().
  • users reset-second-factor <name> on the host removes a user's
    second factor, for the last administrator who lost every device and
    the recovery codes.
  • mailbox-client covers the whole REST API: every operation is a
    method of both clients, named like its operationId. New are the
    administration side (accounts, discovery and the sign-in with a
    code, users and passwords, tokens, roles, second factors, webhooks,
    the audit of administration and of sends, the state of the service,
    the catalogue of rights, health), folders renamed, moved and
    deleted, a message's source (get_message_raw), one message changed
    (update_message), a batch (batch_messages) and the lists across
    accounts (list_all_messages, list_all_changes, list_all_sends).
    They answer records such as Account, User, Grant, Role,
    Token, Webhook and Activity, a list that pages as Paged. A
    secret shown once, a new token, a one-time password or a signing
    secret, comes as a Secret, kept out of repr and str.
  • Python 3.15. The packages support 3.11 to 3.15. The images stay on
    3.14 until a Python 3.15 base image exists.

Changed

  • The MCP server reads its settings as the service does, through
    benethos-mailbox-common[settings], and no longer needs
    python-dotenv. A wrong value in the environment or the settings
    file stops the start and names the setting. The settings file no
    longer changes the environment of the process.
  • The service and the MCP server install a fourth package,
    benethos-mailbox-common, of the same version: what both of them
    need. The MCP server's log is written as the service's: in colour on
    a terminal unless NO_COLOR is set, and its tokens masked as ***.
  • A wrong password or code asked once more before a step, such as the
    recovery key or new recovery codes, counts against the client address
    as a failed sign-in does, not only against the name. Ten from one
    address lock it out, whichever users they were for.
  • A session of the UI ends after 24 hours however it is used
    (MAILBOX_SERVICE_SESSION_MAX_HOURS), and a user holds 10 at most: a
    new sign-in ends the oldest (MAILBOX_SERVICE_SESSIONS_PER_USER). A
    secret shown once, such as a new token, recovery codes or the
    recovery key, waits 5 minutes for the page that shows it
    (MAILBOX_SERVICE_SHOWN_ONCE_MINUTES).
  • Breaking: mailbox-client: list_messages and list_changes take one
    account, account_id is required. list_all_messages and
    list_all_changes read every account. A change of the feed is the
    record Change and a failed id of a batch the record Failed,
    where both were a dict.
  • Breaking: mailbox-client: a message is the record Message,
    a summary in a page, a changed message and a written draft the record
    MessageSummary, where each was a dict. The API's from is
    sender, a time a datetime, an address an Address, an attachment
    of a message an AttachedFile. Change.at is a datetime too.
  • Breaking: mailbox-client: me() is now get_me(), named like
    its operation, as every method of the client is.
  • The UI names the company a person signs in at: "Sign in with
    Google" for Gmail. It offers a sign-in with a code only where the
    provider has one.
  • The UI shows icons, a set of Bootstrap Icons kept in the service, and
    asks before a form that changes much in a dialog of its own. Every
    secret shown once has a copy button. Lists show recent times as
    "3 minutes ago". / puts the cursor in the search field. Nothing is
    loaded from elsewhere.
  • The UI's sidebar ends in an account menu with your page, Password,
    Second factor and Sign out. It folds to icons, and a dot marks
    Accounts or Webhooks when something there needs a look. Mail is now
    Mail search. The Status page is gone: the Accounts list shows
    each account's last sync and error, the overview the sync worker.
  • Lists in the UI create at their head and act on their rows: a plus in
    the card's header, a pencil and a bin on each device, token and the
    folder shown. Several devices or tokens are ticked and removed or
    revoked together, all of them or none, devices after one password
    and one code. A folder is renamed and moved in one form, and a move
    asks first.
  • A user's page in the UI has the tabs Rights, Access and Activity. The
    grant editor shows each grant as a line to change or remove. The
    Users list disables, enables, gives or takes a role of the ticked
    users, all of them or none, and names those it could not change.
  • Each row of the UI's Accounts list links to the account's mail and
    sends. A message has its actions in its header and its keywords as
    chips, and Move asks for the folder in a dialog. The batch bar of a
    mail list asks for a folder only to move, and its head ticks all.

Fixed

  • The MCP server writes a sender's name, a subject and a file name on
    one line each. A line break in them could start a line inside the
    mail's marker that looked like a header of its own.
  • The mail form of the UI shows a name with a double quote or a
    backslash in it so that a reply or a forward reads it back as it was.
  • A Gmail or Microsoft account whose OAuth app is missing from the
    settings no longer breaks every page of the UI. The Accounts list
    says it cannot be used, it counts as needing attention, and it can be
    removed. In GET /v1/status it is not synced and needs
    attention.
  • Changing your password checks the current one as any confirmation
    does: a wrong one counts against your name and is audited as
    auth.confirm_failed.
  • A webhook's accounts cannot be an empty list, which would hear
    nothing. Send null for every account.
  • Changing a webhook needs list_changes only on the accounts the
    change adds. An owner who lost the right on one of its accounts can
    still change the URL or the events.
  • The UI's Users list counts only the users a batch changed, not those
    that were as asked already.
  • mailbox-client: Me has the caller's user_id and name.
  • The text of a mail written as HTML only, on the mail page and as the
    text part of a send, leaves out what its inline style hides as the
    MCP server does: too small, too faint, off the page, cut to nothing,
    or in the colour of its background. An end tag without its start tag
    no longer ends hidden text early.
  • The MCP server's text of an HTML mail keeps a link's address after
    its text and puts a dash before each list item, as the mail page
    does.
  • The text of an HTML mail keeps text whose colour and background are
    alike only by a keyword such as inherit or currentcolor. Only
    colours of their own count, such as #fff, rgb(...) or white.
    left and top far off the page hide text only with position
    absolute, fixed or relative.

Security

  • A secret shown once in the UI goes when its 5 minutes run out, at
    the next request of its session or the next sign-in of anyone, not
    only at the next page that could show it.
  • The audit keeps auth.session_evicted when a ...
Read more

0.3.1 (beta)

Choose a tag to compare

@BeneODev BeneODev released this 08 Oct 12:18
525ad22

Added

  • The project has a logo. Its icon heads each README, and the
    configuration UI shows it in the sidebar and as the favicon. The
    sign-in page shows the logo with its word mark.

Changed

  • containers/dev/ is now containers/development/. This concerns a
    clone of the repository only, not the packages or the images. A
    master key kept in containers/dev/secrets/ moves along by hand.
    The compose project keeps its name mailbox-dev, and with it its
    volume.

0.3.0 (beta)

Choose a tag to compare

@BeneODev BeneODev released this 07 Oct 21:27
96c8a22

The status is beta: usable with real accounts. A breaking change of
the API or the configuration is announced here. Stored data is
carried forward by migrations.

Added

  • benethos-mailbox-client, the Python client of the REST API, as a
    package of its own on PyPI: MailboxClient for async code and
    SyncMailboxClient for code without an event loop, with the same
    methods and records. The MCP server is built on it. Its interface has
    no stability promise yet. Every error is a MailboxError. An answer it
    cannot read, one that is no JSON or lacks a field, is an ApiError
    with the code unexpected_response. from_environment() reads
    MAILBOX_SERVICE_URL, MAILBOX_SERVICE_TOKEN and
    MAILBOX_SERVICE_ALLOW_HTTP once, for a program that makes clients
    for a long time.

  • Microsoft accounts connect without an app of your own: the service
    comes with the project's app, a public client without a secret, used
    when MAILBOX_SERVICE_OAUTH_MICROSOFT_CLIENT_ID is not set. Changing
    between it and an app of your own asks every Microsoft account to sign
    in again.

  • Sign in with a code: POST /v1/oauth/{provider}/device returns a code
    to enter at the provider on any device, and
    POST /v1/oauth/{provider}/device/{sign_in_id} answers with the
    account once the person signed in. It needs no address the provider
    sends a browser back to, so it works on a server. The UI offers it
    beside the sign-in in the browser, when connecting and on an account's
    page. With the project's app away from localhost, it is the only way.

  • MAILBOX_SERVICE_PROVIDERS, a JSON list of the kinds of account a
    deployment offers, every kind without it. Discovery leaves out the
    others, and connecting one answers 501. Accounts connected before
    keep working.

  • JMAP accounts, "provider": "jmap", for Fastmail, Stalwart, Cyrus and
    any JMAP server. The settings name host, port and path of the
    session (default /.well-known/jmap), and auth: password with
    username, or token with the credential token. Everything an IMAP
    account does works, sending included, through JMAP without SMTP
    settings. Ids stay when a message moves, and a message can be in
    several folders (labels). The change feed learns of changes since a
    state, and the server's event source wakes the sync. A deleted message
    is reported without a folder. A replaced draft gets a new id.

  • Discovery asks https://{domain}/.well-known/jmap and the SRV record
    _jmap._tcp, without a credential: jmap is a source, a provider
    of a candidate and a protocol of a server, whose path names the
    session. A JMAP candidate comes before IMAP, and POP3 is offered only
    where neither is. api_token is a new credential. The Fastmail
    preset offers JMAP with an API token. The UI connects a JMAP server
    from discovery or by hand, with a password or an API token.

  • POP3 accounts, "provider": "pop3", for mailboxes without IMAP. The
    inbox is the one folder. Messages are listed newest first, read and
    deleted for good, and sending goes over SMTP. Read state, stars,
    keywords, a delete into the trash, search, folders and drafts answer
    501. The sync polls the mailbox, so the change feed, whats_new and
    webhooks report what arrives and leaves. A server without UIDL is
    refused. Discovery offers POP3 where it finds no IMAP: pop3 is a
    provider of a candidate and a protocol of a server. The UI offers
    POP3 when setting up by hand.

  • capabilities on an account and on each account of /v1/me: what it
    can do beyond reading its inbox. flags, folders and search are
    new. A POP3 account has none of them. The UI offers no action an
    account cannot do, and the MCP server's list_accounts names what an
    account lacks as unsupported.

  • containers/production/: Mailbox in operation from the published
    images, without a clone of the repository. .env names the version,
    the profile https puts Caddy in front with a certificate from Let's
    Encrypt. The MCP server is not part of it by default: each client starts
    one of its own, with its own token, against the service's address. setup.sh does the
    first start: the master key, the keys, the first administrator. Its
    README has the same steps by hand. It keeps the project name
    benethos-mailbox, so the data volume of the compose file before
    stays: copy its secrets/master_key over.

  • MCP server instances beside the service in containers/production/,
    for clients that connect over HTTP. Each is a block in
    compose.override.yaml that extends mcp.yaml, with the profile
    mcp, one user's token and a bearer token of its own. It listens on
    127.0.0.1, or Caddy routes to it under a path of its own, such as
    /mcp/agent, by a file in caddy.d/.

  • MAILBOX_TLS in containers/production/.env: where Caddy's
    certificate comes from. acme is Let's Encrypt as before, internal
    Caddy's own CA, files a certificate of your own in secrets/tls/,
    for a network without access from the internet.

  • Settings and data in the folders of the operating system, for an
    installation without the repository: %LOCALAPPDATA%\benethos-mailbox-service\
    with config and data on Windows, ~/.config/benethos-mailbox-service/
    and ~/.local/share/benethos-mailbox-service/ on Linux. A file named
    with --env-file comes first, then config/benethos-mailbox-service/
    or data/benethos-mailbox-service/ in the working directory, as
    before.

  • benethos-mailbox-service paths names the settings file, the data
    folder, the database and where the master key is.

  • The MCP server reads an optional .env: --env-file or
    MAILBOX_MCP_ENV_FILE, else config/benethos-mailbox-mcp/.env in the
    working directory, else its config folder of the operating system. The
    environment wins over it.

  • service on users and roles, in POST, PATCH and PUT: rights of
    the service, bound to no account. accounts.connect, users.read,
    users.manage, webhooks.manage, audit (below) and admin belong
    there, or single operations of them. /v1/permissions lists these
    groups as service.

  • accounts.connect: discover_account and create_account, which
    were part of accounts.manage, and the sign-in with a code.

  • users.read: list_users, get_user, list_tokens, list_roles and
    get_role, to see users and roles without changing them. users.manage
    keeps every right it had.

  • Whoever connects an account gets accounts.manage on it, unless it
    holds that there already.

  • expires_at on a grant, with a time zone, null for never. An expired
    grant grants nothing and stays until it is removed. A grant is handed
    out for no longer than the giver holds it. The editor has a field
    "Valid until", and the user's page marks an expired grant.

  • /v1/me gives each account a sending list, one entry per grant that
    allows sending there: its recipients, max_sends_per_day and
    sends_left, how many more the limit allows now.

  • The MCP server's list_accounts names these limits beside send, and
    warns of an account where the token may read mail and send it anywhere.

  • The UI's New role page offers four templates that fill the form:
    Reader, Agent, Sender and Operator.

  • folders on a grant: reading, writing and deleting mail only in these
    folders and their subfolders, named by role, name or id. A message or
    folder outside answers 404, a move or a new folder outside 403.
    Deleting to the trash stays allowed, reading the trash needs it in the
    list. Folder lists, message lists, the change feed and webhooks leave
    out the rest, and a reply or forward needs its original in reach. The
    editor has a field "Only in the folders".

  • GET /v1/audit (list_activity): the audit of administration, newest
    first. Sign-ins, failed ones and refused tokens, and who changed users,
    passwords, tokens, roles, accounts and webhooks, with the user, the
    credential, the client address, the record touched, the outcome and
    what was done. Never a secret, never mail content, and a failed sign-in
    with a name that is no user's keeps no name. Filters user,
    activity (a name or its area), record, after, before. Kept for
    MAILBOX_SERVICE_AUDIT_DAYS days, as the audit of sends.

  • audit in service reads it. audit in a grant stays the audit of
    sends. /v1/permissions lists audit among the groups of the service,
    with list_activity in the group.

  • The UI has a page Audit under Service, and a user's page a card Recent
    activity, both for audit in service.

  • Filters on the lists, as the UI has them. Each is optional, and a list
    without one answers as before. GET /v1/sends and
    GET /v1/accounts/{id}/sends: user, outcome, recipient (part of
    an address), after and before (a time with a zone).
    GET /v1/users: name (part of it), role, disabled, ui_sign_in.
    GET /v1/accounts: address (part of it), provider, status.
    GET /v1/webhooks: url (part of it), account, failing.

  • A user in the answers of /v1/users says how it signs in to the UI:
    has_password, must_change (a password set for it, to be changed at
    the next sign-in) and last_sign_in_at. The schema is UserInfo, the
    fields of User and these three. The user's page in the UI names a
    password still to be changed.

  • The UI shows a message's keywords in the list and adds or removes them
    on the message. Those starting with $ stay as they are.

  • accounts on GET /v1/sends: the audit of the named accounts alone,
    as /v1/messages and /v1/changes take it. An account the caller
    may not audit is left out without a word.

  • GET /v1/status (get_status, in accounts.read): the sync worker
    and the accounts the caller may see the status of, as the UI's status
    page shows them. Per account its status, the last pass, the last error
    and whether it needs a look. Nothing is asked ...

Read more