Ga naar inhoud

Mailspace-logs en herstel

Deze endpoints beantwoorden "wat is er met mijn mail gebeurd?" en "kan ik het terugkrijgen?" voor één mailomgeving: het bezorglogboek, het archief met verwijderde berichten die nog terug te zetten zijn, en de mailboxen waarvan de mailserver de mail nog vasthoudt na een definitieve verwijdering. Het aanschaffen, bekijken, resizen en verwijderen van de mailomgeving zelf staat bij de endpoints op planniveau op Mailspace.

Elk pad op deze pagina begint met /api/mailspace/:mailspace_id/…, waarbij :mailspace_id de GUID van de mailomgeving is uit GET /api/mailspace.

OAuth-scopes: reads vereisen mailspace:read, writes vereisen mailspace:write. Sessie- en API-sleutel-credentials slaan de scopecontroles volledig over (zie OAuth). Een OAuth-token zonder de vereiste scope wordt geweigerd met 403 {"error":"insufficient_scope", ...} — de OAuth-foutenvelop, niet de {"errors":[...],"code":"..."}-envelop die elke andere fout op deze pagina gebruikt. Zie Scope-handhavingsfouten.

Alles op deze pagina wordt live van de mailserver gelezen — er is geen lokale tabel die het bezorglogboek of het archief voedt — dus elke aanroep kost een of meer round trips daarheen. Niets op deze pagina is gepagineerd: page en per_page worden hier nergens gelezen. De normale rate limit van de API geldt, dus poll op menselijke tijdschaal en niet in een lus.

Een leeg resultaat is geen bewijs dat er niets aan de hand is

Drie losse mechanismen op deze pagina veranderen een ontbrekende mogelijkheid of een mislukte aanroep naar de mailserver in een lege lijst in plaats van een fout, en geen van de drie wordt aan de client gemeld:

  • bezorgtracing is een Enterprise-functie van de mailserver; waar die niet is gelicentieerd zijn de tracelijsten altijd leeg terwijl de uitgaande wachtrij nog wel gegevens bevat — zie Bezorglogboek;
  • het archiveren van verwijderde mail zit net zo achter Enterprise, en waar het niet beschikbaar is leest het archief als leeg — zie Archief met verwijderde mail;
  • een onbereikbare of falende mailserver zakt bij de arrays van het bezorglogboek én bij de archieflijst terug naar leeg in plaats van een 5xx, omdat die readers de fout opslokken en intern loggen. Alleen de lijst met herstelbare mailboxen antwoordt hier met 503 — zie Leesfouten.

Er is geen enkele manier om die mogelijkheden op te vragen. Deze API kan je niet vertellen op welke tier de mailserver draait, en gokt er niet naar. Toon nooit "geen bezorgproblemen" of "niets te herstellen" op grond van een lege reactie alleen.

Gedeelde controles

Elk endpoint op deze pagina erft dezelfde keten van controles, in deze volgorde toegepast voordat de actie draait. De eerste controle die faalt, beantwoordt het verzoek.

Geldt voor Voorwaarde Reactie
alle mailhosting is niet geconfigureerd op het platform 503 stalwart_unavailable
alle :mailspace_id onbekend, of niet zichtbaar voor je credential 404, lege body
writes gebruiker heeft geen bewerkrecht op de eigen workspace van de mailomgeving 403 not_authorized
alle mailomgeving is geblokkeerd (door support of wegens een onbetaalde factuur) 403 mailspace_suspended
writes mailomgeving is met soft delete verwijderd (wacht op verwijdering) 403 pending_delete
alle mailomgeving is nog niet voorzien 409 not_provisioned

Een write wordt bepaald door het HTTP-verb, niet door het endpoint

De twee write-controles beslissen op basis van de request-methode: GET en HEAD gaan er zo door, al het andere wordt gecontroleerd. Op deze pagina is Een gearchiveerd item terugzetten dus een write omdat het een POST is, en is Een gearchiveerd bericht downloaden een read omdat het een GET is — ook al levert de download een volledig bericht op.

Een lid met alleen leesrechten dat een mailspace:write-token heeft, kan daarom het bezorglogboek lezen, het archief doorzoeken, berichten downloaden en herstelbare mailboxen opvragen, en krijgt 403 not_authorized bij elk terugzetten en verwijderen: aan de scope van het token is voldaan, aan het recht van het lid niet.

HEAD wordt precies als GET behandeld, dus een read-endpoint met HEAD aftasten geeft dezelfde status terug als de GET zou doen.

Reads overleven de bewaartermijn, writes niet

Een met soft delete verwijderde mailomgeving (die op verwijderen wacht) blijft de hele bewaartermijn reads beantwoorden — je kunt haar logs nog lezen, haar gearchiveerde mail opvragen en haar herstelbare mailboxen opvragen. Elke mutatie wordt geweigerd met 403 pending_delete totdat de mailomgeving is hersteld. Zie Een mailomgeving verwijderen.

Een geblokkeerde mailomgeving gedraagt zich anders: mailspace_suspended blokkeert reads net zo goed als writes. Een mailomgeving die op verwijderen wacht is van die controle uitgezonderd, dus de twee codes gelden nooit samen.

Een onbekende of buiten je bereik vallende :mailspace_id antwoordt 404 met een lege body, niet met de gebruikelijke foutenvelop — een GUID die bij een andere workspace hoort moet niet te onderscheiden zijn van een die niet bestaat.


Bezorglogboek

GET /api/mailspace/:mailspace_id/logs

Scope mailspace:read.

Alles wat de mailserver weet over de recente mail van deze mailomgeving, in vier losse arrays: de bezorgtraces incoming en outgoing, de live uitgaande queue, en issues — de afgeleide lijst van bezorgingen die zijn mislukt of nog opnieuw worden geprobeerd.

Dit endpoint neemt helemaal geen parameters: geen filters, geen datumbereik, geen paginering. De reader haalt per dataset maximaal de 100 meest recente entries op — maximaal 100 traces, die daarna over incoming en outgoing worden verdeeld (interne mail telt in beide mee), maximaal 100 wachtrij-entries en maximaal 100 issues — en de mailserver heeft niets om door te pagineren. Intern vraagt één aanroep de traces en de wachtrij één keer op per domein dat de mailomgeving host, dus het zijn meerdere round trips: dit is geen endpoint om kort achter elkaar te pollen.

De vier arrays zijn niet even goed beschikbaar — lees dit voordat je erop bouwt

Ze komen uit verschillende objecten op de mailserver, en juist daarom worden ze onder aparte keys teruggegeven in plaats van samengevoegd in één lijst:

  • incoming / outgoing komen uit de bezorgtracing van de mailserver, en dat is een functie alleen voor Enterprise. Waar die niet is gelicentieerd zijn beide arrays altijd leeg, hoeveel mail er ook is verwerkt.
  • queue komt uit de uitgaande wachtrij, die op elke build bestaat. Die bevat gegevens, ongeacht de licentie.
  • issues is de vereniging van de twee — permanente fouten komen uit de traces, berichten die nog opnieuw worden geprobeerd komen uit de wachtrij — dus die is gedeeltelijk aangetast waar tracing niet beschikbaar is: berichten die nog opnieuw worden geprobeerd staan er wel in, afgeronde bounces niet.

Doordat ze apart blijven, kan een client de degradatie zien: een gevulde queue terwijl beide trace-arrays leeg zijn, betekent dat tracing op deze installatie niet beschikbaar is, niet dat er geen mail is verwerkt. Samengevoegd zou "Enterprise niet gelicentieerd" niet te onderscheiden zijn geweest van "geen mailproblemen".

Een client mag lege trace-arrays niet lezen als "geen mailproblemen", en er is geen vlag die je in plaats daarvan kunt controleren — de API krijgt niet te horen op welke tier de mailserver zit, dus die kan het je niet vertellen. Moet je integratie de twee onderscheiden, vergelijk dan de trace-arrays met queue en behandel "traces blijvend leeg" als een eigenschap van de installatie.

Interne mail staat er twee keer in, met opzet

Een bericht van de ene mailbox naar de andere binnen dezelfde mailomgeving is echt beide richtingen: het loopt via de lokale wachtrij weg, dus het classificeert als inkomend, terwijl iemand in de mailomgeving het wel degelijk heeft verstuurd. Het staat daarom onder zowel incoming als outgoing, met internal: true erbij zodat je het kunt labelen in plaats van dubbel te tellen. Zonder dat zou een klant die een bericht zoekt dat hij een collega stuurde, het nooit onder Uitgaand vinden.

Teruggegeven params
  • incoming: Array<Object> | traces voor mail die aan de mailboxen van deze mailomgeving is bezorgd
  • outgoing: Array<Object> | traces van bezorgpogingen naar andere mailservers, plus elk intern bericht
  • queue: Array<Object> | berichten die nog in de uitgaande wachtrij staan
  • issues: Array<Object> | permanente fouten (uit de traces) en berichten die nog opnieuw worden geprobeerd (uit de wachtrij)

Elke entry in incoming en outgoing is een trace:

  • id: String | null — de trace-id van de mailserver, zonder controle doorgegeven, dus een trace die de mailserver zonder id vastlegde komt binnen als null. Gebruik dit veld niet zonder controle als sleutel of om de tracearrays te ontdubbelen — de garantie dat het nooit null is geldt voor queue en issues hieronder, en niet hier
  • timestamp: String | ISO 8601, zoals de mailserver het heeft vastgelegd
  • from: String | de envelope-afzender, zoals de mailserver die heeft vastgelegd
  • to: String | ontvangers als komma-gescheiden string, niet als array. Bij mail van een afzender buiten deze mailomgeving worden mede-ontvangers op domeinen die deze mailomgeving niet host verwijderd voordat je het ziet
  • subject: String | null als de mailserver er geen heeft vastgelegd
  • size: Integer | null — bytes
  • direction: String | incoming of outgoing
  • internal: Boolean | true voor mail van mailbox naar mailbox binnen deze mailomgeving (zie hierboven)
  • status: String | afgeleid uit het SMTP-log: delivered, bounced, failed, retrying of sending
  • events: Array<Object> | het SMTP-log, in de volgorde waarin de mailserver het heeft vastgelegd
    • name: String | de naam van de mailserver-event, bijv. delivery.attempt-start
    • timestamp: String | ISO 8601
    • key_values: Array<Object> | de detailvelden van de event, als lijst in plaats van als object omdat dezelfde key legitiem kan terugkomen binnen één event
      • key: String
      • value: String, Integer of Boolean | onbewerkt doorgegeven van de mailserver

Elke entry in queue en issues is een bericht in de wachtrij:

  • id: String | de eigen wachtrij-id van de mailserver bij een wachtrij-entry, en een stabiele kunstmatige trace-<…>-id bij een issue dat uit een trace is afgeleid. Nooit null in deze twee arrays — die garantie geldt specifiek voor deze twee en niet voor de eigen id van een trace. Zie de waarschuwing hieronder
  • source: String | queue of trace — uit welke dataset de entry komt. Aanwezig op elke entry van beide arrays queue en issues; de trace-arrays bevatten het veld niet. Zie de waarschuwing hieronder voor waar het voor dient
  • created_at: String | ISO 8601
  • return_path: String | de envelope-afzender. Leeg betekent een null return path (<>): het bericht is een bouncemelding
  • subject: String | null
  • size: Integer | null — bytes
  • flags: Array<String> | vlaggen van de mailserver, bijv. dsnSent zodra er een bouncemelding is verstuurd
  • recipients: Array<Object>
    • address: String
    • status: String | Completed, TemporaryFailure of PermanentFailure; null zolang de ontvanger nog in de wachtrij staat en er niets is geprobeerd
    • retry_due: String | null — ISO 8601, wanneer de volgende poging volgt
    • retry_count: Integer | null
    • error: Object | null wanneer de mailserver geen foutdetails voor die ontvanger heeft gemeld
      • type: String | null — het fouttype van de mailserver
      • response_code: Integer | null — de SMTP-statuscode. Wordt onbewerkt doorgegeven, dus een issue dat uit een trace komt bevat wat het SMTP-log heeft vastgelegd en kan een String teruggeven
      • response_enhanced: String | null — de enhanced status code, bijv. 5.1.1
      • response_hostname: String | null — de server die antwoordde
      • message: String | null — de tekst van de andere server
  • events: Array<Object> | het SMTP-log in dezelfde vorm als bij een trace. Leeg bij een live wachtrij-entry, die nog geen trace heeft; alleen gevuld bij een issue dat uit een trace komt

Leid de status op berichtniveau zelf af

Een bericht in de wachtrij heeft geen status-veld op het hoogste niveau — bewust, zodat de regel op één plek staat in plaats van gedupliceerd te worden. Leid hem af uit recipients en flags:

  1. een ontvanger op PermanentFailurebounced als flags dsnSent bevat, anders failed;
  2. anders een ontvanger op TemporaryFailureretrying;
  3. anders alle ontvangers op Completeddelivered;
  4. anders → sending.

Een issues-entry is geen wachtrij-entry, ook al heeft die dezelfde vorm

issues wordt weergegeven in de vorm van een bericht in de wachtrij, uit welke bron het ook komt; dat maakt het uniform om te tonen maar makkelijk om te overinterpreteren:

  • Een issue dat uit een trace is afgeleid (een afgeronde bounce of weigering) heeft geen wachtrij-id om mee te dragen en krijgt daarom een stabiele kunstmatige trace-<…>-id — op elke poll dezelfde waarde, dus een client mag erop sleutelen of dedupliceren. Zijn recipients-array bevat één kunstmatige entry waarvan de address de hele komma-gescheiden ontvangerstring van de trace is, met status PermanentFailure, retry_due en retry_count op null, en error.type op null. Zijn flags is ["dsnSent"] wanneer er een bouncemelding is verstuurd en [] wanneer het bericht zonder melding mislukte, en het bevat het volledige SMTP-log in events.
  • Een issue dat uit de wachtrij komt is een echte wachtrij-entry, vermeld met alleen zijn falende ontvangers — zijn recipients-array is dus een subset van die onder queue — en zijn events is leeg.

source onderscheidt de twee zonder dat je ze hoeft te inspecteren. Elke entry van queue en van issues bevat het veld: "queue" voor een rij die uit de live uitgaande wachtrij komt, "trace" voor een rij die uit een trace is afgeleid. Het zegt of de rij nog kan veranderen — een wachtrij-entry wordt nog opnieuw geprobeerd, terwijl een issue uit een trace definitief is: de bouncemelding is al verstuurd en het bericht is al uit de wachtrij verdwenen — en het is de enige manier om te zien welke entries een events-log bevatten. De trace-arrays (incoming en outgoing) bevatten het niet: alleen de rijen in wachtrijvorm krijgen het stempel.

Permanente fouten komen altijd alleen uit traces en berichten die nog opnieuw worden geprobeerd altijd alleen uit de wachtrij, dus de twee bronnen tellen hetzelfde bericht nooit dubbel. Dat betekent ook dat afgeronde bounces bij de dingen horen die verdwijnen waar tracing niet is gelicentieerd.

curl -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "X-Auth-Account: $ACCOUNT_ID" \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/logs

Archief met verwijderde mail

Mail die een gebruiker heeft verwijderd en die de mailserver nog vasthoudt. Een item komt hier terecht wanneer een bericht wordt verwijderd, en blijft tot zijn archived_until-deadline, waarna de mailserver het definitief opruimt. De bewaartermijn geldt serverbreed en kan via deze API niet worden verlengd of verkort.

Een leeg archief betekent twee dingen, geen drie

Het archief is een object alleen voor Enterprise op de mailserver, en de lijst erachter is strikt: een query die niet kon worden uitgevoerd antwoordt 503 archived_items_unavailable. Gearchiveerde mail opvragen antwoordt 200 met een lege archived_items-array in twee situaties, en niets in de reactie onderscheidt ze:

  • er is niets gearchiveerd;
  • de mailserver licentieert archiveren niet. Er is geen manier om die mogelijkheden op te vragen, dus "niet gelicentieerd" en "niets gearchiveerd" zijn bewust niet te onderscheiden — en elk endpoint op id antwoordt dan 404 unknown_archived_item.

"De mailserver kon niet gevraagd worden" is niet langer een van die situaties: dat is de 503. Dat de licentiegrens nog wel terugzakt naar een lege lijst komt doordat de mailserver een verzoek om een gelicentieerd object wel beantwoordt, en een antwoord is precies waar de strikte read op controleert; een server die geen antwoord geeft levert de 503. Een onbereikbare mailserver is dus wél te onderscheiden van een leeg archief. Een niet-gelicentieerde niet.

Behandel een leeg archief als "niets te tonen", nooit als "er is nooit iets verwijderd".

Item-id's zijn opaak, en op de mailserver zijn ze globaal

Een gearchiveerd item wordt geadresseerd met het :stalwart_id-segment, de opake id uit de lijst — geen GUID en niets dat je zelf kunt samenstellen. Op de mailserver is het een ongescope handle, dus CloudPress resolvet elk id bij elk verzoek op id opnieuw tegen de eigen mailboxen van deze mailomgeving en antwoordt 404 unknown_archived_item wanneer het hier niet thuishoort. Een item-id uit een andere workspace is op alle vier de endpoints op id onbereikbaar, download inbegrepen.

Vertak op code, nooit op errors[0]

Alle drie de acties die tegen het archief kunnen falen — Gearchiveerde mail opvragen, Een gearchiveerd item terugzetten en Een gearchiveerd item verwijderen — beantwoorden een fout met een zorgvuldig gekozen Engelse zin en geven nooit de eigen fouttekst van de mailserver door; die is interne techniek waar een client niets mee kan.

Die berichten zijn Engels, welke Accept-Language je ook stuurt, ook al zet die header nog steeds de taal voor de rest van het verzoek, en de formulering mag veranderen. De code is het contract.

Gearchiveerde mail opvragen

GET /api/mailspace/:mailspace_id/archived_items

Scope mailspace:read.

Elk terug te zetten verwijderd bericht in alle mailboxen van de mailomgeving, het laatst gearchiveerde eerst. Het archief wordt per mailbox opgevraagd en gefilterd op de eigen mailboxen van deze mailomgeving, dus het kan nooit de verwijderde mail van een andere tenant tonen. Niet gepagineerd: CloudPress begrenst de query op 500 items per mailbox, en alles daarboven valt weg zonder markering in de reactie.

Teruggegeven params
  • archived_items: Array
    • id: String | de opake item-id — dit is het :stalwart_id-segment voor elk endpoint op id hieronder
    • type: String | de variant van het item. In de praktijk Email; de mailserver kent andere maar schrijft ze nooit
    • status: String | archived, of requestRestore zodra er een terugzetting voor is ingepland. De mailserver laat het veld weg zolang het op zijn standaardwaarde staat, dus het wordt ingevuld als archived
    • subject: String | null
    • from: String | null
    • size: Integer | null — bytes
    • received_at: String | null — ISO 8601, wanneer het bericht origineel aankwam
    • archived_at: String | null — ISO 8601, wanneer het naar het archief is verwijderd
    • archived_until: String | null — ISO 8601, de deadline voor definitieve verwijdering. Serverbrede bewaartermijn, niet per item
    • mailbox_email: String | het adres waaruit het bericht is verwijderd
    • mailbox_id: String | de GUID van die mailbox

De blob- en account-handles van het item op de mailserver worden bewust niet meegegeven: beide zijn globale, ongescope handles, en het bericht zelf komt terug via Een gearchiveerd bericht downloaden.

Fouten
  • 503 archived_items_unavailable | de archiefquery kon niet worden uitgevoerd. Geen clientfout, en dus opnieuw te proberen — zie Leesfouten. Een ongelicentieerd archief zakt nog wel terug naar de lege 200 die hierboven staat; een onbereikbare mailserver niet
  • plus de gedeelde controles

Een gearchiveerd item bekijken

GET /api/mailspace/:mailspace_id/archived_items/:stalwart_id

Scope mailspace:read. Eén item, met dezelfde velden als de lijst, onder een archived_item-object.

De body van het bericht komt hier niet terug — gebruik Een gearchiveerd bericht downloaden voor het originele bericht.

Fouten
  • 404 unknown_archived_item | zo'n item bestaat niet, of het hoort niet bij deze mailomgeving
  • 503 archived_item_lookup_unavailable | de eigendomscontrole kon niet worden uitgevoerd, dus het item is niet bevestigd en niet ontkend. Het opnieuw proberen waard, en geen teken dat het item weg is
  • plus de gedeelde controles

Een gearchiveerd item terugzetten

POST /api/mailspace/:mailspace_id/archived_items/:stalwart_id/restore

Scope mailspace:write (het is een POST, dus het wordt als write gecontroleerd — zie hierboven).

Dit meldt een verzoek, geen afgeronde terugzetting

De mailserver kent geen synchrone terugzetting. De trigger is het zetten van de status van het item op een terugzetverzoek, dat de server vervolgens op zijn eigen schema verwerkt, en hij geeft geen signaal wanneer het klaar is — daarom zegt de reactie restore_queued en nooit restored, en heeft deze API niets om te pollen.

Wil je weten of een terugzetting daadwerkelijk is gebeurd, lees het archief dan opnieuw: een item dat is teruggezet staat er niet meer in, en het bericht komt onder een nieuw id terug in de mailbox. Meld een terugzetting niet als afgerond op grond van deze reactie.

Teruggegeven params
  • status: String | altijd "restore_queued"
  • archived_item: Object | de velden van het item, exact als in de lijst

Twee verschillende status-velden

De status op het hoogste niveau beschrijft jouw verzoek (restore_queued). De status binnen archived_item is het eigen veld van het item, zoals het stond toen het item werd opgezocht — dus een eerste terugzetting laat hier archived zien, ook al is het verzoek geaccepteerd, en een tweede op hetzelfde item laat requestRestore zien. Lees het item opnieuw in plaats van dit veld als de uitkomst te lezen.

Fouten
  • 404 unknown_archived_item | zo'n item bestaat niet, of het hoort niet bij deze mailomgeving. Ook wat je krijgt als het item tussen het opzoeken en de terugzetting zelf verdwijnt — zie de note hieronder
  • 422 restore_failed | de mailserver heeft geantwoord en de terugzetting geweigerd. Er is niets in de wachtrij gezet
  • 503 archived_item_lookup_unavailable | de eigendomscontrole kon niet worden uitgevoerd, dus er is niets verstuurd naar de mailserver. Het item is onaangeroerd
  • 503 restore_unconfirmed | onbepaald. De herstelopdracht is verstuurd en de uitkomst is onbekend, dus de terugzetting staat mogelijk al in de wachtrij
  • plus de gedeelde controles, inclusief 403 not_authorized en 403 pending_delete

Twee 503-codes die tegengestelde dingen beloven

archived_item_lookup_unavailable betekent dat er niets is geprobeerd. restore_unconfirmed betekent dat er wél iets is geprobeerd en dat de uitkomst onbekend is — lees het item, of het archief, dus opnieuw voordat je het opnieuw probeert, in plaats van aan te nemen dat de terugzetting is mislukt.

Dit is de tegengestelde belofte van restore_unavailable bij het herstel van een definitief verwijderde mailbox, dat betekent dat er niets is gebeurd. Generaliseer niet over die twee vlakken op grond van het woord "unavailable" — lees de code.

De race met een verdwenen item antwoordt 404, niet 422 — een bewuste wijziging van het contract

De id van het item wordt twee keer geresolveerd: één keer door de gedeelde controle voordat de actie draait, en nog een keer binnen de terugzetting of de wissing zelf. Een item dat tussen die twee verdwijnt — realistisch gezien een client die zijn eigen verlopen verzoek opnieuw doet — antwoordt nu 404 unknown_archived_item, dezelfde code die de controle zelf teruggeeft bij een misser, omdat het hetzelfde feit is.

Voorheen antwoordde dit 422 restore_failed, en 422 delete_failed bij Een gearchiveerd item verwijderen. Dat is bewust gewijzigd nu deze endpoints nog niet in productie draaien: een client die op 422 vertakt voor een verdwenen item zal dat niet meer zien. restore_failed en delete_failed houden hun 422 voor een echte weigering door de mailserver.

Een gearchiveerd bericht downloaden

GET /api/mailspace/:mailspace_id/archived_items/:stalwart_id/download

Scope mailspace:read — een GET, dus een read voor zowel de scope als de write-controles, ook al levert het een heel bericht op.

Dit is het enige endpoint hier dat geen JSON teruggeeft

Een geslaagde download is het originele RFC822-bericht als ruwe bytes: Content-Type: message/rfc822 met een attachment-disposition. Het is geen base64 in een JSON-envelop — dat zou de payload opblazen en elke client dwingen te decoderen. De bytes gaan via CloudPress in plaats van rechtstreeks naar de mailserver te linken, omdat de eigen download-URL van de mailserver niet op jouw mailomgeving is gescopet.

Fouten zijn nog steeds JSON, in de gebruikelijke {"errors":[...],"code":"..."}-envelop, dus vertak op de responsestatus voordat je de body als bericht behandelt.

De bestandsnaam van de bijlage wordt afgeleid uit het eigen opgeslagen onderwerp van het bericht en daarna opgeschoond, zodat een kwaadwillend onderwerp het opgeslagen bestand niet kan sturen en niet uit de header kan ontsnappen: elk teken buiten letters, cijfers, underscores, spaties, streepjes en punten wordt _, reeksen _ worden er één, een punt aan het begin verdwijnt, en spaties aan begin en eind gaan eraf. Blijft er dan niets over — het bericht had helemaal geen onderwerp, of het opschonen maakte de stam leeg — dan wordt de item-id gebruikt, dus de bestandsnaam is nooit een kale .eml. Wat er ook wordt gebruikt, het wordt daarna afgekapt op 80 tekens voordat .eml eraan wordt geplakt.

De bestandsnaam varieert niet met Accept-Language. Hij komt uit het onderwerp zoals de mailserver het heeft opgeslagen, nooit uit een vertaald label voor "geen onderwerp", dus hetzelfde item downloadt voor elke client onder dezelfde naam.

curl -o message.eml \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "X-Auth-Account: $ACCOUNT_ID" \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/archived_items/$ITEM_ID/download
Fouten
  • 404 unknown_archived_item | zo'n item bestaat niet, of het hoort niet bij deze mailomgeving
  • 404 / 503 download_unavailable | het bericht kon niet worden geleverd, en de status is het verschil: 404 wanneer de mailserver heeft geantwoord dat het opgeslagen bericht weg is (definitief — stop), 503 bij elke andere leesfout (het bericht kan er nog zijn — probeer het opnieuw). Eén code, omdat het één feit over één resource is; de status zegt wat je nu moet doen
  • 503 archived_item_lookup_unavailable | de eigendomscontrole kon niet worden uitgevoerd
  • plus de gedeelde controles

Een gearchiveerd item verwijderen

DELETE /api/mailspace/:mailspace_id/archived_items/:stalwart_id

Scope mailspace:write.

Onomkeerbaar — er zit geen tweede archief achter dit archief

Dit wist het gearchiveerde bericht en de opgeslagen kopie ervan vóór de bewaardeadline. Daarna is er niets meer te herstellen, en er is geen bevestigingsparameter: de enige voorwaarde is dat het item bij deze mailomgeving hoort.

Elke geslaagde wissing wordt met de gebruikte credential vastgelegd in het auditlog van het platform, nadat de mailserver het heeft bevestigd — een logregel voor een vernietiging die niet is gebeurd zou erger zijn dan geen regel.

Een item dat al weg is antwoordt 404 unknown_archived_item, niet 200 — de id wordt tegen de mailserver geresolveerd voordat er iets wordt verwijderd, dus een dubbele DELETE en een item dat op zijn eigen deadline is opgeruimd komen beide daar terecht. Vanuit het oogpunt van de client is dit endpoint niet idempotent.

Teruggegeven params
  • deleted: Boolean | altijd true
  • id: String | het item dat is gewist
  • subject: String | null — vastgelegd vóór de wissing
Fouten
  • 404 unknown_archived_item | zo'n item bestaat niet, of het hoort niet bij deze mailomgeving. Ook wat je krijgt als het item tussen het opzoeken en de wissing zelf verdwijnt — zie de note hierboven
  • 422 delete_failed | de mailserver heeft geantwoord en de wissing geweigerd. Er is niets vernietigd
  • 503 archived_item_lookup_unavailable | de eigendomscontrole kon niet worden uitgevoerd, dus er is niets verstuurd. Het item is onaangeroerd
  • 503 delete_unconfirmed | onbepaald, en deze wissing is onomkeerbaar: het item kan al weg zijn
  • plus de gedeelde controles

Lees bij delete_unconfirmed eerst het item terug voordat je iets anders doet

Het is de enige plek op dit vlak waar een onbekende uitkomst niet terug te draaien is, dus dit behandelen als "het verwijderen is mislukt" is de slechtste van de drie mogelijke aannames. Vraag het item op: een 404 betekent dat de wissing is doorgekomen.

Bij geen van beide fouten wordt een auditregel geschreven. Die regel wordt pas geschreven nadat de mailserver de wissing bevestigt, dus het ontbreken ervan is geen bewijs dat het item er nog is.


Definitief verwijderde mailboxen

Mailboxen die CloudPress al definitief heeft verwijderd — het lokale record is weg — en waarvan de mailserver de mail nog vasthoudt, omdat het verwijderen van een mailaccount niets meteen wist: het account verdwijnt en er wordt een wistaak ingepland voor het eind van de bewaartermijn van de server. Tot die taak afgaat, kan het account met zijn oorspronkelijke id worden teruggehaald.

Deze horen bewust niet bij de mailboxlijst van de mailomgeving, die dat bakje volledig weglaat — daarom hebben ze hun eigen endpoint.

Herstelbare mailboxen opvragen

GET /api/mailspace/:mailspace_id/purged_mailboxes

Scope mailspace:read.

De definitief verwijderde mailboxen die nog hersteld kunnen worden, geordend op recoverable_until zodat de mailbox die op het punt staat verloren te gaan eerst komt. Niet gepagineerd.

De lijst wordt tegen de mailserver gecontroleerd en niet alleen uit de lokale status geleverd: de bewaartermijn is een serverbrede instelling die support kan verkorten, en een vervroegde wissing verwijdert een account meteen — beide laten een lokaal record achter waarvan de deadline nog in de toekomst ligt terwijl de mail al weg is. Een record waarvoor de mailserver geen wistaak meer heeft, wordt volledig weggelaten, omdat een herstel aanbieden dat niet kan worden nagekomen erger is dan er geen aanbieden. De controle kost twee round trips — de wistaken, en de domeinlookup die ze resolvet — en helemaal geen als er niets te controleren is.

Een genoemde mailbox is echt; een lege lijst is geen bewijs dat de mail weg is

De controle is een strikte lees, dus een mailserver die helemaal niet gevraagd kan worden antwoordt 503 purged_mailboxes_unavailable in plaats van een lege 200 — zie Leesfouten. Dat sluit het ene gat, maar niet het andere.

De wistaken worden 500 rijen per keer over de hele mailserver gelezen en daarna teruggefilterd naar deze mailomgeving, en de reactie bevat geen totaal en geen signaal dat er is afgekapt. Op een drukke server kunnen de taken van deze mailomgeving buiten dat venster vallen, waardoor een mailbox die de mailserver nog zou herstellen stil ontbreekt in een volkomen gezonde 200.

Lees de lijst dus zoals het veilig is: een record dat erin staat is werkelijk herstelbaar, en een lege lijst betekent "niets gevonden", niet "de mail is weg". Gooi op grond daarvan nooit je eigen laatste record van een mailbox weg — houd een kopie van wat je verwijderde tot de eigen recoverable_until van dat record is verstreken.

Teruggegeven params
  • purged_mailboxes: Array
    • id: String | de GUID van het record — dit is het :guid-segment dat Een definitief verwijderde mailbox herstellen neemt
    • email: String | het adres dat zou terugkomen
    • display_name: String | null
    • purged_at: DateTime | wanneer CloudPress de mailbox definitief verwijderde
    • recoverable_until: DateTime | de eigen wisdeadline van de mailserver, bij het verwijderen gespiegeld — nooit een lokaal berekende termijn
    • days_remaining: Integer | hele dagen die resteren, met 0 als ondergrens

De opgeslagen quota, aliassen, lidmaatschappen en de account-id op de mailserver worden bewust niet meegegeven: dat is input voor het herstel, geen beschrijving van de verwijderde mailbox.

Fouten
  • 503 purged_mailboxes_unavailable | de mailserver kon niet gevraagd worden welke accounts hij nog vasthoudt, dus de lijst kan niet worden beantwoord. Opnieuw te proberen, en bewust geen lege 200 — een client zou die lezen als "niets te herstellen"
  • plus de gedeelde controles

Een definitief verwijderde mailbox herstellen

PATCH /api/mailspace/:mailspace_id/purged_mailboxes/:guid

Scope mailspace:write.

Annuleert de openstaande wissing op de mailserver en bouwt de mailbox opnieuw op. De mailserver reconstrueert het account uitsluitend uit de wistaak, die alleen zijn naam en domein bevat, dus al het andere wordt teruggeschreven uit de momentopname die CloudPress bij het verwijderen bewaarde.

password is verplicht, omdat een herstelde mailbox zonder inloggegevens terugkomt

De mailserver geeft het account helemaal zonder wachtwoord terug, dus dit endpoint stelt er een in. Daarmee is het een write die inloggegevens uitdeelt: het levert een werkende mailbox op een adres dat mail ontvangt. Een ontbrekend of leeg password wordt geweigerd met 400 password_blank voordat er iets wordt aangeraakt, en elk succes wordt met de gebruikte credential vastgelegd in het auditlog van het platform.

Params
  • password: String (required) | het wachtwoord waarmee de herstelde mailbox terugkomt. Een lege of ontbrekende waarde is 400 password_blank

De mailbox komt terug met zijn weergavenaam en met zijn opgeslagen quota — die wordt naar beneden bijgesteld in plaats van geweigerd als het pakket er geen ruimte meer voor heeft, dus een mailbox kan iets kleiner terugkomen dan hij was.

Zijn aliassen worden op dezelfde voorwaarden opnieuw toegepast als zijn lidmaatschappen hieronder: de aliasschrijfactie laat stilzwijgend elk adres vallen waarvan het domein niet kan worden geresolveerd, en meldt toch succes. Een 200 is geen bewijs dat elke alias terug is — lees de mailbox terug en vergelijk.

Lidmaatschappen van groepen en mailinglijsten worden opnieuw toegepast, maar zijn niet gegarandeerd

De lidmaatschappen van groepen en mailinglijsten uit de momentopname worden teruggeschreven waar die groepen en lijsten nog bestaan; een die sindsdien is verwijderd wordt overgeslagen, en een fout tijdens het opnieuw toepassen laat het herstel niet mislukken. Een 200 hier betekent dus niet dat de lidmaatschappen terug zijn. Lees de groepen en lijsten van de mailbox daarna na en voeg toe wat ontbreekt.

Het record wordt bij succes verbruikt, dus een tweede PATCH op dezelfde :guid antwoordt 404 unknown_purged_mailbox.

Teruggegeven params
  • mailbox: Object | dezelfde vorm die de endpoints voor het verwijderen en herstellen van een mailbox teruggeven
    • id: String | de GUID van de herstelde mailbox
    • email: String
    • status: String | active
    • scheduled_deletion_at: DateTime | null
    • days_until_deletion: Integer | null

Een mailbox kan onherstelbaar worden terwijl hij nog in de lijst staat

Herstelbaarheid is een beslissing van de mailserver, en die kan tussen je lijstaanroep en je herstel veranderen. Er zijn drie uitkomsten, en alleen de laatste is het opnieuw proberen waard:

  • De eigen deadline van het record is verstreken → 409 not_recoverable. Er is niets aangeraakt, en dit is met opnieuw proberen niet tot een succes te maken.
  • De mailserver is gevraagd en antwoordde dat hij geen wistaak meer heeft voor het account (de bewaartermijn is verkort, of het account is op verzoek gewist) → 409 not_recoverable, en de momentopname wordt weggegooid, dus dezelfde :guid antwoordt bij een nieuwe poging 404 unknown_purged_mailbox en de mailbox valt uit de lijst. Ook dit is niet tot een succes te maken — de mail is werkelijk weg. Het deelt de code, de status en de formulering met het geval van de verstreken deadline hierboven, omdat het hetzelfde feit is: definitief, niet opnieuw proberen.
  • De mailserver kon niet gevraagd worden — de voorafgaande lees mislukte, of kwam afgezwakt terug → 503 restore_unavailable, en de momentopname blijft precies staan waar hij stond. Probeer het opnieuw.

Die lees is bewust strikt, en de momentopname is de enige kopie van de quota, weergavenaam, aliassen en lidmaatschappen van de mailbox: een mislukte lees mag nooit worden aangezien voor "de mailserver heeft hem gewist" en het record vernietigen van een mailbox die de server nog zou teruggeven. De momentopname wordt daarom alleen na een geslaagde lees weggegooid — nooit na een mislukte.

Vertak op de status, en de drie antwoorden op drie verschillende vragen: 409 betekent dat er niets meer aan te doen is, 422 dat de mailserver heeft geweigerd en opnieuw proberen kan helpen, 503 dat de controle niet kon worden gedaan en er niets is geprobeerd — de enige fout hier waarvan gezegd kan worden dat er niets is veranderd. Je hoeft de lijst niet opnieuw te lezen om uit te zoeken welke fout je kreeg.

De lijst is om dezelfde reden geordend op wat het eerst verloopt: herstel in die volgorde.

restore_failed is het opnieuw proberen waard, en dekt het definitieve geval niet meer

503 restore_unavailable dekt alleen de voorafgaande lees — die waarmee wordt vastgesteld of de mailserver het account nog vasthoudt. Een transportfout later in het herstel, nadat die beslissing is genomen, komt nog steeds naar buiten als 422 restore_failed; die code bewijst dus niet dat de mailserver nee zei. Het herstel is idempotent, en daarom is opnieuw proberen de juiste zet bij alles wat tijdelijk lijkt.

Wat restore_failed niet meer dekt, is het ene geval waarin opnieuw proberen nooit kan werken: een account dat de mailserver al heeft gewist antwoordt in plaats daarvan 409 not_recoverable. Je hoeft de lijst dus niet meer opnieuw te lezen om een 422 die het opnieuw proberen waard is te onderscheiden van een definitieve.

download_unavailable bij Een gearchiveerd bericht downloaden blijft een uitzondering op de 503-conventie die Leesfouten beschrijft — die code draagt beide statussen: 404 voor een bericht dat volgens de mailserver weg is, 503 voor een lees die hij niet kon uitvoeren.

restore_failed dekt ook een herstel dat de mailserver of het pakket weigert:

  • het adres is opnieuw in gebruik door een nieuwe mailbox — verwijder of hernoem die eerst en herstel deze daarna;
  • de mailboxlimiet van het pakket is al vol — verwijder eerst een andere mailbox of upgrade;
  • elke andere weigering van de mailserver.
curl -X PATCH \
  -H "Authorization: Bearer $CLOUDPRESS_TOKEN" \
  -H "X-Auth-Account: $ACCOUNT_ID" \
  -H "Content-Type: application/json" \
  -d '{"password": "a-strong-password"}' \
  https://my.cloudpress.com/api/mailspace/$MAILSPACE_ID/purged_mailboxes/$PURGED_ID
Fouten
  • 404 unknown_purged_mailbox | zo'n record bestaat niet in deze mailomgeving, of het is al verbruikt
  • 409 not_recoverable | definitief — niet opnieuw proberen. Of het herstelvenster is gesloten, of de voorafgaande lees is gelukt en meldde dat de mailserver het account niet meer vasthoudt. Op dat tweede pad wordt het record onderweg verbruikt, dus een nieuwe poging antwoordt 404
  • 400 password_blank | geen wachtwoord meegegeven
  • 422 restore_failed | de mailserver heeft geantwoord en geweigerd — zie hierboven. Het opnieuw proberen waard
  • 503 restore_unavailable | de voorafgaande lees van de lijst met wistaken van de mailserver kon niet worden uitgevoerd, dus er is niets geprobeerd. Het record is onaangeraakt — probeer het opnieuw
  • plus de gedeelde controles

Foutcodes

Alle fouten behalve de OAuth-scopefout gebruiken de standaardenvelop {"errors": [...], "code": "..."} die is beschreven in Foutreacties. Een onbekende of buiten je bereik vallende :mailspace_id is de uitzondering in de andere richting: 404 met een lege body en helemaal geen envelop. Bij Een gearchiveerd bericht downloaden zijn de fouten JSON, ook al is een succes dat niet.

Gedeeld door elk endpoint op deze pagina — zie Gedeelde controles:

Code Status Opgeworpen bij
stalwart_unavailable 503 alle verzoeken
not_authorized 403 POST, PATCH, DELETE
mailspace_suspended 403 alle verzoeken
pending_delete 403 POST, PATCH, DELETE
not_provisioned 409 alle verzoeken

Per endpoint:

Code Status Opgeworpen door
archived_items_unavailable 503 gearchiveerde mail opvragen
purged_mailboxes_unavailable 503 herstelbare mailboxen opvragen
unknown_archived_item 404 een gearchiveerd item bekijken, terugzetten, downloaden, verwijderen; ook een terugzetting of verwijdering waarvan het item tussen de twee id-lookups verdwijnt
archived_item_lookup_unavailable 503 een gearchiveerd item bekijken, terugzetten, downloaden, verwijderen — de eigendomscontrole kon niet worden uitgevoerd, dus er is niets verstuurd
restore_failed 422 een gearchiveerd item terugzetten; een definitief verwijderde mailbox herstellen
restore_unconfirmed 503 een gearchiveerd item terugzetten — de opdracht is verstuurd en de uitkomst is onbekend
restore_unavailable 503 een definitief verwijderde mailbox herstellen — er is niets geprobeerd
download_unavailable 404 / 503 een gearchiveerd bericht downloaden — 404 het bericht is weg (definitief), 503 elke andere leesfout (opnieuw te proberen)
delete_failed 422 een gearchiveerd item verwijderen
delete_unconfirmed 503 een gearchiveerd item verwijderen — de wissing is verstuurd en kan al zijn uitgevoerd. Onomkeerbaar
unknown_purged_mailbox 404 een definitief verwijderde mailbox herstellen
not_recoverable 409 een definitief verwijderde mailbox herstellen
password_blank 400 een definitief verwijderde mailbox herstellen

Het endpoint voor het bezorglogboek heeft geen eigen foutcodes — alleen de gedeelde controles.