Release or deny messages in batches

When you need to act on many quarantined messages at once, use the batch methods of the customers.quarantinedMessages resource in the Mail Admin Quarantine API:

Both methods are asynchronous. The API accepts the request, returns a long-running operation, and processes the messages in the background. You then poll the get method of the customers.operations resource until the operation is done and read the per-message results from the response.

Compared to calling the single-message methods in a loop, batch methods count as one write request against your quota usage regardless of how many messages they contain.

Prerequisites

Start a batch operation

To release messages in a batch, send a POST request to the :batchRelease custom method on the customers.quarantinedMessages resource. The requests array contains one object per message, each with the message's resource name:

POST https://mailadminquarantine.googleapis.com/v1/customers/CUSTOMER_ID/quarantinedMessages:batchRelease
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json

{
  "requests": [
    {"name": "customers/CUSTOMER_ID/quarantinedMessages/user1@example.com:18d3ef45a8b2c1d0"},
    {"name": "customers/CUSTOMER_ID/quarantinedMessages/user2@example.com:18d3ef45a8b2c1d1"}
  ]
}

To deny messages in a batch, send the same request body to the :batchDeny custom method on the customers.quarantinedMessages resource instead:

POST https://mailadminquarantine.googleapis.com/v1/customers/CUSTOMER_ID/quarantinedMessages:batchDeny

Each request must contain between 1 and 500 entries. Every message must belong to the customer in the request path. A request with more than 500 entries returns a 400 INVALID_ARGUMENT error.

The API responds immediately with an Operation resource:

{
  "name": "customers/01abc23de/operations/7f1c2a0e-5b7d-4d3a-9c1e-2f4b6a8d0e13",
  "metadata": {
    "@type": "type.googleapis.com/google.gmail.mailadminquarantine.v1.BatchReleaseQuarantinedMessagesMetadata",
    "state": "PROCESSING",
    "processedCount": 0,
    "totalCount": 2
  },
  "done": false
}

Store the name of the operation. You need it to check the operation's progress.

Check the status of a batch operation

To check the progress of a batch operation, send a GET request to the get method on the operation's resource name:

GET https://mailadminquarantine.googleapis.com/v1/customers/CUSTOMER_ID/operations/OPERATION_ID
Authorization: Bearer ACCESS_TOKEN

The get method accepts either Admin Quarantine API scope, so a read-only integration can monitor operations that another system started.

While the operation runs, the done field is set to false and the metadata field reports progress:

Metadata field Description
state PROCESSING while messages are being processed, then SUCCEEDED or FAILED.
processedCount The number of messages processed so far.
totalCount The total number of messages in the batch.

When the operation finishes, the done field is set to true and the response field contains a result for every message in the batch:

{
  "name": "customers/01abc23de/operations/7f1c2a0e-5b7d-4d3a-9c1e-2f4b6a8d0e13",
  "metadata": {
    "@type": "type.googleapis.com/google.gmail.mailadminquarantine.v1.BatchReleaseQuarantinedMessagesMetadata",
    "state": "SUCCEEDED",
    "processedCount": 2,
    "totalCount": 2
  },
  "done": true,
  "response": {
    "@type": "type.googleapis.com/google.gmail.mailadminquarantine.v1.BatchReleaseQuarantinedMessagesResponse",
    "results": [
      {
        "name": "customers/01abc23de/quarantinedMessages/user1@example.com:18d3ef45a8b2c1d0",
        "status": {},
        "quarantinedMessage": {
          "name": "customers/01abc23de/quarantinedMessages/user1@example.com:18d3ef45a8b2c1d0",
          "messageId": "18d3ef45a8b2c1d0",
          "messageOwner": "user1@example.com",
          "state": "RELEASED"
        }
      },
      {
        "name": "customers/01abc23de/quarantinedMessages/user2@example.com:18d3ef45a8b2c1d1",
        "status": {
          "code": 5,
          "message": "Quarantined message not found"
        }
      }
    ]
  }
}

Each entry in results describes the outcome for one message:

Result field Description
name The resource name of the message.
status A google.rpc.Status. An empty object (code 0, OK) means the action succeeded. Otherwise, code and message explain why the message was skipped.
quarantinedMessage The updated message with its new state. Present only when status is OK.

A batch operation succeeds even if some individual messages fail. Always inspect the status of each result rather than relying on the operation's state alone.

If the whole operation fails, for example because the backend is unavailable, the done field is set to true and the error field contains a google.rpc.Status instead of response.

Example: Batch release and poll for completion

The following Python example starts a batch release and polls the operation every few seconds until it completes:

import time
import requests

BASE_URL = 'https://mailadminquarantine.googleapis.com/v1'
CUSTOMER_ID = '01abc23de'  # Your customer ID without the leading 'C'.
headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json',
}

# message_names is a list of up to 500 quarantined message resource names.
batch_url = f'{BASE_URL}/customers/{CUSTOMER_ID}/quarantinedMessages:batchRelease'
body = {'requests': [{'name': name} for name in message_names]}
operation = requests.post(batch_url, headers=headers, json=body)
operation.raise_for_status()
operation_name = operation.json()['name']

while True:
  status = requests.get(f'{BASE_URL}/{operation_name}', headers=headers)
  status.raise_for_status()
  operation = status.json()
  if operation.get('done'):
    break
  metadata = operation.get('metadata', {})
  print(f"Processed {metadata.get('processedCount', 0)} of "
        f"{metadata.get('totalCount', 0)} messages...")
  time.sleep(5)

if 'error' in operation:
  raise RuntimeError(f"Batch failed: {operation['error']}")

for result in operation['response'].get('results', []):
  if result.get('status', {}).get('code', 0) == 0:
    print(f"Released: {result['name']}")
  else:
    print(f"Skipped {result['name']}: {result['status'].get('message')}")

Next steps