Code examples
Before you run an example
These are HTTP client examples for the implemented MailRaft API, not language SDKs. Configure MAILRAFT_API_URL with your approved local or staging API origin and MAILRAFT_API_KEY with a scoped server-side key. Never place keys in browser code. Requests have timeouts and do not automatically follow redirects or retry.
The scope shown for each operation is required. Use resources belonging to the key’s company. Read responses can contain private content; do not paste them into logs or support messages. Source paths below are relative to the MailRaft repository.
Dashboard
Project source: apps/web/src/review/FernlyOverview.tsx
List inbox messages
POST /v1/call · Scope: messages:read
Returns up to 50 non-deleted messages for the company bound to the key.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"messages.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"messages.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"messages.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"messages.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"messages.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"messages.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"messages.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"messages.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"messages.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"messages.list","payload":{}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
Read an operation
POST /v1/call · Scope: operations:read
Replace the example UUID with an operation belonging to your company. Provider acceptance and delivery are separate states.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
Inbox & drafts
Project source: apps/web/src/review/ReviewApp.tsxapps/web/src/review/WorkspacePages.tsx
List inbox messages
POST /v1/call · Scope: messages:read
Returns up to 50 non-deleted messages for the company bound to the key.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"messages.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"messages.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"messages.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"messages.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"messages.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"messages.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"messages.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"messages.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"messages.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"messages.list","payload":{}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
Approvals
Project source: apps/web/src/review/WorkspacePages.tsx
Human approval is required. The dashboard review flow is a local preview; there is no public approvals-list endpoint.
No public API example is available for this page yet. Consult the source files for the current UI implementation.
Domains
Project source: apps/web/src/review/DomainList.tsxapps/web/src/review/AddDomain.tsx
Persisted tenant domains with DNS TXT ownership verification. SES sender activation is separate. Requires migration 038 and the configured API/database service.
Create domain
POST /v1/call · Scope: domains:manage
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.create","payload":{"name":"updates.example.com"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Create domain. Required key scope: domains:manage.
# Replace the name with a domain you own; save the returned id and TXT record.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Create domain. Required key scope: domains:manage.
# Replace the name with a domain you own; save the returned id and TXT record.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.create","payload":{"name":"updates.example.com"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Create domain. Required key scope: domains:manage.
// Replace the name with a domain you own; save the returned id and TXT record.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Create domain. Required key scope: domains:manage.
# Replace the name with a domain you own; save the returned id and TXT record.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.create\",\"payload\":{\"name\":\"updates.example.com\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Create domain. Required key scope: domains:manage.
# Replace the name with a domain you own; save the returned id and TXT record.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.create","payload":{"name":"updates.example.com"}}'Implementation: apps/api/src/domain-service.ts
List domains
POST /v1/call · Scope: domains:read
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List domains. Required key scope: domains:read.
# Retrieve the domains belonging to the workspace bound to this key.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List domains. Required key scope: domains:read.
# Retrieve the domains belonging to the workspace bound to this key.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List domains. Required key scope: domains:read.
// Retrieve the domains belonging to the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List domains. Required key scope: domains:read.
# Retrieve the domains belonging to the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List domains. Required key scope: domains:read.
# Retrieve the domains belonging to the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.list","payload":{}}'Implementation: apps/api/src/domain-service.ts
Retrieve domain
POST /v1/call · Scope: domains:read
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.get","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Retrieve domain. Required key scope: domains:read.
# Replace the id with a saved domain id from your workspace.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Retrieve domain. Required key scope: domains:read.
# Replace the id with a saved domain id from your workspace.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.get","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Retrieve domain. Required key scope: domains:read.
// Replace the id with a saved domain id from your workspace.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Retrieve domain. Required key scope: domains:read.
# Replace the id with a saved domain id from your workspace.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.get\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Retrieve domain. Required key scope: domains:read.
# Replace the id with a saved domain id from your workspace.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.get","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: apps/api/src/domain-service.ts
Verify domain
POST /v1/call · Scope: domains:manage
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.verify","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Read ownership_status; verified ownership does not activate email sending.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Verify domain. Required key scope: domains:manage.
# Add the returned TXT record at your DNS provider before checking ownership.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Read ownership_status; verified ownership does not activate email sending.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Verify domain. Required key scope: domains:manage.
# Add the returned TXT record at your DNS provider before checking ownership.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Read ownership_status; verified ownership does not activate email sending.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.verify","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Read ownership_status; verified ownership does not activate email sending.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Read ownership_status; verified ownership does not activate email sending.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Read ownership_status; verified ownership does not activate email sending.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Verify domain. Required key scope: domains:manage.
// Add the returned TXT record at your DNS provider before checking ownership.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Read ownership_status; verified ownership does not activate email sending.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Verify domain. Required key scope: domains:manage.
# Add the returned TXT record at your DNS provider before checking ownership.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.verify\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Verify domain. Required key scope: domains:manage.
# Add the returned TXT record at your DNS provider before checking ownership.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.verify","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: apps/api/src/domain-service.ts
Update domain
POST /v1/call · Scope: domains:manage
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.update","payload":{"id":"00000000-0000-4000-8000-000000000001","sender_name":"Your business"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Update domain. Required key scope: domains:manage.
# Replace the id and send only the settings you want to change.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Update domain. Required key scope: domains:manage.
# Replace the id and send only the settings you want to change.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.update","payload":{"id":"00000000-0000-4000-8000-000000000001","sender_name":"Your business"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Update domain. Required key scope: domains:manage.
// Replace the id and send only the settings you want to change.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Update domain. Required key scope: domains:manage.
# Replace the id and send only the settings you want to change.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"sender_name\":\"Your business\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Update domain. Required key scope: domains:manage.
# Replace the id and send only the settings you want to change.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.update","payload":{"id":"00000000-0000-4000-8000-000000000001","sender_name":"Your business"}}'Implementation: apps/api/src/domain-service.ts
Delete domain
POST /v1/call · Scope: domains:manage
Owner-only writes. Replace the domain or ID with your own. Verification checks the returned TXT record; it does not activate email sending.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"domains.delete","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Delete domain. Required key scope: domains:manage.
# Replace the id with the domain you intend to remove.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Delete domain. Required key scope: domains:manage.
# Replace the id with the domain you intend to remove.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"domains.delete","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Delete domain. Required key scope: domains:manage.
// Replace the id with the domain you intend to remove.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Delete domain. Required key scope: domains:manage.
# Replace the id with the domain you intend to remove.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"domains.delete\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Delete domain. Required key scope: domains:manage.
# Replace the id with the domain you intend to remove.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"domains.delete","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: apps/api/src/domain-service.ts
Team & tasks
Project source: apps/web/src/review/TeamTasks.tsx
Team and task controls are local review state. A public task-assignment API is not implemented.
No public API example is available for this page yet. Consult the source files for the current UI implementation.
Contacts
Project source: apps/web/src/review/ContactsPage.tsx
Create contact
POST /v1/call · Scope: contacts:manage
Owner-only. Defaults to no marketing consent; opt_in requires evidence.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.create","payload":{"email":"person@example.com","name":"Your contact"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Create contact. Required key scope: contacts:manage.
# Replace the contact details. New contacts default to no marketing consent.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Create contact. Required key scope: contacts:manage.
# Replace the contact details. New contacts default to no marketing consent.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.create","payload":{"email":"person@example.com","name":"Your contact"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Create contact. Required key scope: contacts:manage.
// Replace the contact details. New contacts default to no marketing consent.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Create contact. Required key scope: contacts:manage.
# Replace the contact details. New contacts default to no marketing consent.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.create\",\"payload\":{\"email\":\"person@example.com\",\"name\":\"Your contact\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Create contact. Required key scope: contacts:manage.
# Replace the contact details. New contacts default to no marketing consent.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.create","payload":{"email":"person@example.com","name":"Your contact"}}'Implementation: apps/api/src/contact-service.ts
List contacts
POST /v1/call · Scope: contacts:read
Returns data, counts and next_cursor. Pass next_cursor as payload.cursor to fetch the next 100 contacts. A record is not marketing consent.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.list","payload":{}}'Implementation: supabase/migrations/202610150039_contact_management.sql
Import contacts
POST /v1/call · Scope: contacts:manage
Atomic batch of up to 100 contacts. Duplicates preserve existing consent.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.import","payload":{"contacts":[{"email":"person@example.com"}]}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Import contacts. Required key scope: contacts:manage.
# Import up to 100 contacts; duplicates keep their existing consent.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Import contacts. Required key scope: contacts:manage.
# Import up to 100 contacts; duplicates keep their existing consent.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.import","payload":{"contacts":[{"email":"person@example.com"}]}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Import contacts. Required key scope: contacts:manage.
// Import up to 100 contacts; duplicates keep their existing consent.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Import contacts. Required key scope: contacts:manage.
# Import up to 100 contacts; duplicates keep their existing consent.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.import\",\"payload\":{\"contacts\":[{\"email\":\"person@example.com\"}]}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Import contacts. Required key scope: contacts:manage.
# Import up to 100 contacts; duplicates keep their existing consent.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.import","payload":{"contacts":[{"email":"person@example.com"}]}}'Implementation: apps/api/src/contact-service.ts
Update contact
POST /v1/call · Scope: contacts:manage
Withdrawal also records suppression and suppresses pending campaign recipients. Opt-in cannot remove suppression.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.update","payload":{"id":"00000000-0000-4000-8000-000000000001","permission":"withdrawn"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Update contact. Required key scope: contacts:manage.
# Replace the id. Withdrawing consent also adds the contact to suppression.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Update contact. Required key scope: contacts:manage.
# Replace the id. Withdrawing consent also adds the contact to suppression.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.update","payload":{"id":"00000000-0000-4000-8000-000000000001","permission":"withdrawn"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Update contact. Required key scope: contacts:manage.
// Replace the id. Withdrawing consent also adds the contact to suppression.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Update contact. Required key scope: contacts:manage.
# Replace the id. Withdrawing consent also adds the contact to suppression.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.update\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\",\"permission\":\"withdrawn\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Update contact. Required key scope: contacts:manage.
# Replace the id. Withdrawing consent also adds the contact to suppression.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.update","payload":{"id":"00000000-0000-4000-8000-000000000001","permission":"withdrawn"}}'Implementation: apps/api/src/contact-service.ts
Campaigns
Project source: apps/web/src/review/WorkspacePages.tsx
The example reads potential contacts only. Campaign sends still require consent, suppression checks and human approval.
List contacts
POST /v1/call · Scope: contacts:read
Returns data, counts and next_cursor. Pass next_cursor as payload.cursor to fetch the next 100 contacts. A record is not marketing consent.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.list","payload":{}}'Implementation: supabase/migrations/202610150039_contact_management.sql
Connectors
Project source: apps/web/src/review/WorkspacePages.tsx
List connectors
GET /v1/integrations · Scope: integrations:read
The existing API uses integrations in its URL. Dashboard terminology is Connectors.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/integrations', {
method: 'GET',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/integrations'
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
request = urllib.request.Request(url, method='GET',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'})
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/integrations')
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/integrations');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
request, err := http.NewRequest("GET", strings.TrimRight(base, "/") + "/v1/integrations", nil)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
let response = client.get(format!("{}/v1/integrations", base.trim_end_matches('/')))
.bearer_auth(key)
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/integrations"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.GET().build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Get, baseUrl.TrimEnd('/') + "/v1/integrations");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/integrations")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:get, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request GET "${MAILRAFT_API_URL%/}/v1/integrations" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json'Implementation: apps/api/src/server.ts
Webhooks
Project source: apps/web/src/review/DeveloperPages.tsxapps/public-edge/server.mjs
Provider callback verification exists. A public API for creating, updating or deleting customer webhook subscriptions is not implemented. See the Webhooks guide for verified callback boundaries.
No public API example is available for this page yet. Consult the source files for the current UI implementation.
API and MCP
Project source: apps/web/src/review/DeveloperPages.tsxapps/api/src/server.ts
List inbox messages
POST /v1/call · Scope: messages:read
Returns up to 50 non-deleted messages for the company bound to the key.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"messages.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"messages.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"messages.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"messages.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"messages.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"messages.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"messages.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List inbox messages. Required key scope: messages:read.
// Retrieve up to 50 messages belonging to the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"messages.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"messages.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List inbox messages. Required key scope: messages:read.
# Retrieve up to 50 messages belonging to the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"messages.list","payload":{}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
List connectors
GET /v1/integrations · Scope: integrations:read
The existing API uses integrations in its URL. Dashboard terminology is Connectors.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
const response = await fetch(base.replace(/\/$/, '') + '/v1/integrations', {
method: 'GET',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/integrations'
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
request = urllib.request.Request(url, method='GET',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'})
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/integrations')
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
$curl = curl_init(rtrim($base, '/') . '/v1/integrations');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
request, err := http.NewRequest("GET", strings.TrimRight(base, "/") + "/v1/integrations", nil)
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
let response = client.get(format!("{}/v1/integrations", base.trim_end_matches('/')))
.bearer_auth(key)
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/integrations"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.GET().build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List connectors. Required key scope: integrations:read.
// Retrieve the saved connectors for the workspace bound to this key.
using var request = new HttpRequestMessage(HttpMethod.Get, baseUrl.TrimEnd('/') + "/v1/integrations");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/integrations")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:get, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List connectors. Required key scope: integrations:read.
# Retrieve the saved connectors for the workspace bound to this key.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request GET "${MAILRAFT_API_URL%/}/v1/integrations" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json'Implementation: apps/api/src/server.ts
List contacts
POST /v1/call · Scope: contacts:read
Returns data, counts and next_cursor. Pass next_cursor as payload.cursor to fetch the next 100 contacts. A record is not marketing consent.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"contacts.list","payload":{}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"contacts.list\",\"payload\":{}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"contacts.list\",\"payload\":{}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"contacts.list","payload":{}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"contacts.list\",\"payload\":{}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"contacts.list\",\"payload\":{}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"contacts.list\",\"payload\":{}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// List contacts. Required key scope: contacts:read.
// For the next page, pass the returned next_cursor as payload.cursor.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"contacts.list\",\"payload\":{}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"contacts.list\",\"payload\":{}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# List contacts. Required key scope: contacts:read.
# For the next page, pass the returned next_cursor as payload.cursor.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"contacts.list","payload":{}}'Implementation: supabase/migrations/202610150039_contact_management.sql
Search knowledge
POST /v1/call · Scope: knowledge:read
Returns up to five matching ready knowledge chunks within the authorized company.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"knowledge.search","payload":{"query":"opening hours"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"knowledge.search","payload":{"query":"opening hours"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"knowledge.search","payload":{"query":"opening hours"}}'Implementation: supabase/migrations/202609170006_workflows.sql
Read an operation
POST /v1/call · Scope: operations:read
Replace the example UUID with an operation belonging to your company. Provider acceptance and delivery are separate states.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
Plan & billing
Project source: apps/web/src/review/BillingPage.tsx
Billing is arranged manually. Plan requests open an email draft; there is no checkout API and no automatic payment or plan activation.
No public API example is available for this page yet. Consult the source files for the current UI implementation.
Knowledge
Project source: apps/web/src/review/WorkspacePages.tsx
Search knowledge
POST /v1/call · Scope: knowledge:read
Returns up to five matching ready knowledge chunks within the authorized company.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"knowledge.search","payload":{"query":"opening hours"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"knowledge.search","payload":{"query":"opening hours"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"knowledge.search","payload":{"query":"opening hours"}}'Implementation: supabase/migrations/202609170006_workflows.sql
Assistant
Project source: apps/web/src/review/WorkspacePages.tsx
Knowledge search supports grounding. This example does not run an AI model or send a message.
Search knowledge
POST /v1/call · Scope: knowledge:read
Returns up to five matching ready knowledge chunks within the authorized company.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"knowledge.search","payload":{"query":"opening hours"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"knowledge.search","payload":{"query":"opening hours"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Search knowledge. Required key scope: knowledge:read.
// Replace the query with the information you want to find in workspace knowledge.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"knowledge.search\",\"payload\":{\"query\":\"opening hours\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Search knowledge. Required key scope: knowledge:read.
# Replace the query with the information you want to find in workspace knowledge.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"knowledge.search","payload":{"query":"opening hours"}}'Implementation: supabase/migrations/202609170006_workflows.sql
Activity
Project source: apps/web/src/review/WorkspacePages.tsx
Read an operation
POST /v1/call · Scope: operations:read
Replace the example UUID with an operation belonging to your company. Provider acceptance and delivery are separate states.
Node.js 22+. Save as example.mjs and run node example.mjs.
// Load your API origin and scoped key from server environment variables.
const base = process.env.MAILRAFT_API_URL;
const key = process.env.MAILRAFT_API_KEY;
if (!base || !key) throw new Error('Set MAILRAFT_API_URL and MAILRAFT_API_KEY');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
const response = await fetch(base.replace(/\/$/, '') + '/v1/call', {
method: 'POST',
// Authenticate this request with the scoped bearer key.
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}),
signal: AbortSignal.timeout(10000),
redirect: 'error',
});
// Check the HTTP status before using the response.
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// Use the returned JSON in your application.
const data = await response.json();
// Use data in your application; do not log private message content.Python 3; standard library only. Run python3 example.py.
import json, os, urllib.request
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
# Load your API origin and scoped key from server environment variables.
url = os.environ['MAILRAFT_API_URL'].rstrip('/') + '/v1/call'
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = urllib.request.Request(url, method='POST',
headers={'Authorization': 'Bearer ' + os.environ['MAILRAFT_API_KEY'],
'Content-Type': 'application/json'},
data="{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}".encode('utf-8'))
# Send the request; HTTP errors raise before the response is decoded.
with urllib.request.build_opener(NoRedirect).open(request, timeout=10) as response:
# Use the returned JSON in your application.
data = json.load(response)
# Use data without logging private content.Ruby 3; standard library only. Run ruby example.rb.
require 'net/http'
require 'json'
# Load your API origin and scoped key from server environment variables.
uri = URI(ENV.fetch('MAILRAFT_API_URL').sub(/\/$/, '') + '/v1/call')
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer ' + ENV.fetch('MAILRAFT_API_KEY')
request['Content-Type'] = 'application/json'
request.body = "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"
# Send the request with bounded connection and read timeouts.
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 10) { |http| http.request(request) }
# Check the HTTP status before using the response.
raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
# Use the returned JSON in your application.
data = JSON.parse(response.body)PHP 8 with the cURL extension. Run php example.php.
<?php
// Load your API origin and scoped key from server environment variables.
$base = getenv('MAILRAFT_API_URL');
$key = getenv('MAILRAFT_API_KEY');
if (!$base || !$key) throw new RuntimeException('Set API environment variables');
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
$curl = curl_init(rtrim($base, '/') . '/v1/call');
curl_setopt_array($curl, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_TIMEOUT => 10,
]);
// Send the configured request.
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
curl_close($curl);
// Check the HTTP status before using the response.
if ($result === false || $status < 200 || $status >= 300) throw new RuntimeException('Request failed: ' . $status);
// Use the returned JSON in your application.
$data = json_decode($result, true, 512, JSON_THROW_ON_ERROR);Go 1.22+; standard library only. Run go run example.go.
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
)
func main() {
// Load your API origin and scoped key from server environment variables.
base, key := os.Getenv("MAILRAFT_API_URL"), os.Getenv("MAILRAFT_API_KEY")
if base == "" || key == "" { panic("Set API environment variables") }
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
request, err := http.NewRequest("POST", strings.TrimRight(base, "/") + "/v1/call", strings.NewReader("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + key)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}
// Send the request using the bounded HTTP client.
response, err := client.Do(request)
if err != nil { panic(err) }
defer response.Body.Close()
// Check the HTTP status before using the response.
if response.StatusCode < 200 || response.StatusCode >= 300 { panic(fmt.Sprintf("HTTP %d", response.StatusCode)) }
// Use the returned JSON in your application.
var data any
if err := json.NewDecoder(response.Body).Decode(&data); err != nil { panic(err) }
}Rust 2021; add reqwest 0.12 with blocking and json features, and serde_json 1.
use std::{env, time::Duration};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your API origin and scoped key from server environment variables.
let base = env::var("MAILRAFT_API_URL")?;
let key = env::var("MAILRAFT_API_KEY")?;
let client = reqwest::blocking::Client::builder()
.timeout(Duration::from_secs(10))
.redirect(reqwest::redirect::Policy::none()).build()?;
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
let response = client.post(format!("{}/v1/call", base.trim_end_matches('/')))
.bearer_auth(key)
.header("Content-Type", "application/json")
.body("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")
.send()?;
// Check the HTTP status before using the response.
if !response.status().is_success() { return Err(format!("HTTP {}", response.status()).into()); }
// Use the returned JSON in your application.
let _data: serde_json::Value = response.json()?;
Ok(())
}Java 17+; standard library only. Save as Example.java and run java Example.java.
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
class Example {
public static void main(String[] args) throws Exception {
// Load your API origin and scoped key from server environment variables.
String base = System.getenv("MAILRAFT_API_URL");
String key = System.getenv("MAILRAFT_API_KEY");
if (base == null || key == null) throw new IllegalStateException("Set API environment variables");
var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NEVER).build();
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
var request = HttpRequest.newBuilder(URI.create(base.replaceAll("/+$", "") + "/v1/call"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", "Bearer " + key)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}")).build();
// Send the request and receive the response body.
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
// Check the HTTP status before using the response.
if (response.statusCode() < 200 || response.statusCode() >= 300)
throw new IllegalStateException("HTTP " + response.statusCode());
// Use the JSON response body with your application's JSON parser.
String data = response.body();
}
}.NET 8+ console project; replace Program.cs and run dotnet run.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
// Load your API origin and scoped key from server environment variables.
var baseUrl = Environment.GetEnvironmentVariable("MAILRAFT_API_URL") ?? throw new Exception("Set MAILRAFT_API_URL");
var key = Environment.GetEnvironmentVariable("MAILRAFT_API_KEY") ?? throw new Exception("Set MAILRAFT_API_KEY");
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
// Read an operation. Required key scope: operations:read.
// Replace the id with an operation belonging to your workspace.
using var request = new HttpRequestMessage(HttpMethod.Post, baseUrl.TrimEnd('/') + "/v1/call");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", key);
request.Content = new StringContent("{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}", Encoding.UTF8, "application/json");
// Send the request using the bounded HTTP client.
using var response = await client.SendAsync(request);
// Check the HTTP status before using the response.
response.EnsureSuccessStatusCode();
// Use the returned JSON in your application.
using var data = JsonDocument.Parse(await response.Content.ReadAsStringAsync());Elixir 1.16+ with OTP inets and ssl. Run elixir example.exs.
:inets.start()
:ssl.start()
# Load your API origin and scoped key from server environment variables.
base = System.fetch_env!("MAILRAFT_API_URL")
key = System.fetch_env!("MAILRAFT_API_KEY")
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
url = String.to_charlist(String.trim_trailing(base, "/") <> "/v1/call")
headers = [{~c"authorization", String.to_charlist("Bearer " <> key)}]
request = {url, headers, ~c"application/json", "{\"operation\":\"operations.read\",\"payload\":{\"id\":\"00000000-0000-4000-8000-000000000001\"}}"}
options = [timeout: 10_000, autoredirect: false, ssl: [verify: :verify_peer, cacerts: :public_key.cacerts_get(), customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]]]
# Send the request and match its HTTP status before using the body.
case :httpc.request(:post, request, options, [body_format: :binary]) do
{:ok, {{_, status, _}, _, data}} when status in 200..299 ->
# data is the JSON response; parse with your application's JSON library.
data
{:ok, {{_, status, _}, _, _}} -> raise "HTTP #{status}"
{:error, reason} -> raise inspect(reason)
endcURL 7.76+ with MAILRAFT_API_URL and MAILRAFT_API_KEY set in your server environment.
# Set MAILRAFT_API_URL to your configured local or staging API origin.
# Set MAILRAFT_API_KEY in your server environment; never ship it to the browser.
: "${MAILRAFT_API_URL:?Set MAILRAFT_API_URL}"
: "${MAILRAFT_API_KEY:?Set MAILRAFT_API_KEY}"
# Read an operation. Required key scope: operations:read.
# Replace the id with an operation belonging to your workspace.
# Print the JSON response; --fail-with-body reports HTTP errors.
curl --fail-with-body --silent --show-error --max-time 10 \
--request POST "${MAILRAFT_API_URL%/}/v1/call" \
--header "Authorization: Bearer $MAILRAFT_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"operation":"operations.read","payload":{"id":"00000000-0000-4000-8000-000000000001"}}'Implementation: supabase/migrations/202609170007_scoped_keys.sql
Settings
Project source: apps/web/src/review/SettingsPages.tsx
Settings use authenticated dashboard sessions. No public bearer-key settings API is documented.
No public API example is available for this page yet. Consult the source files for the current UI implementation.
Help & support
Project source: apps/web/src/review/SupportPages.tsx
Help and support are dashboard pages, not public API operations.
No public API example is available for this page yet. Consult the source files for the current UI implementation.