Quickstart
Five steps from an empty account to a delivered email. Nothing here is a placeholder — every call is one you can make right now.
What you need
- An account and a team. Sign up and the first team is created for you.
- A domain you can add DNS records to.
- Something that can make an HTTPS request.
Every request goes to https://api.rasket.com. Until that hostname is attached to the deployment, the identical routes answer at https://<deployment-host>/api/v1/… — that fallback is what works today, and nothing else about the request changes.
1. Create an API key
Create the first one on the dashboard, since you need a key before you can call this endpoint. After that, keys can create keys.
curl -X POST "https://api.rasket.com/api-keys" \
-H "Authorization: Bearer $RASKET_API_KEY" \
-H "User-Agent: acme-billing/1.0" \
-H "Content-Type: application/json" \
-d '{
"name": "billing worker",
"permission": "sending_access",
"domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
}'const response = await fetch("https://api.rasket.com/api-keys", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
"User-Agent": "acme-billing/1.0",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "billing worker",
permission: "sending_access",
domain_id: "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
}),
});
const { id } = await response.json();import os
import requests
response = requests.post(
"https://api.rasket.com/api-keys",
headers={
"Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
"User-Agent": "acme-billing/1.0",
},
json={
"name": "billing worker",
"permission": "sending_access",
"domain_id": "d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34"
},
)
id = response.json()["id"]The token in the response is shown once and never again. Put it somewhere your code can read it before you close the terminal:
export RASKET_API_KEY="rk_live_2f7a9c1d8e3b5074a6c2f019d4b83e5a"Every step below is a plain HTTPS request, shown in curl, JavaScript and Python. To call a client library instead, install the one for your language:
# Nothing to install: every example below is a plain HTTPS request.
curl --versionnpm install rasketpip install rasketOr let an agent do it: an AI coding agent can run this whole page for you, in this order, from the recipe on the agents page.
2. Add your sending domain
Use a subdomain — send.acme.example rather than acme.example — so your transactional reputation stays separate from everything else your domain does.
curl -X POST "https://api.rasket.com/domains" \
-H "Authorization: Bearer $RASKET_API_KEY" \
-H "User-Agent: acme-billing/1.0" \
-H "Content-Type: application/json" \
-d '{
"name": "send.acme.example",
"region": "eu-west-1"
}'const response = await fetch("https://api.rasket.com/domains", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
"User-Agent": "acme-billing/1.0",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "send.acme.example",
region: "eu-west-1"
}),
});
const { id } = await response.json();import os
import requests
response = requests.post(
"https://api.rasket.com/domains",
headers={
"Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
"User-Agent": "acme-billing/1.0",
},
json={
"name": "send.acme.example",
"region": "eu-west-1"
},
)
id = response.json()["id"]3. Publish the DNS records
The response carries a records array: a DKIM TXT record and the two MAIL FROM records. Publish all three at your DNS provider, exactly as given. The names are relative to the domain you just added, which is how nearly every provider's form expects them.
Three records are all a first send needs. Turning on open or click tracking later adds a fourth, and the tracking guide covers that on its own — you do not need it to send.
If your DNS is on Cloudflare, POST /domains/{domain_id}/autoconfigure will publish them for you with a scoped API token. The full record set, why each one is there, and how to claim a domain another team already verified are on the domains reference.
4. Verify the domain
DNS takes a few minutes to propagate. Ask us to check as soon as you have published; we also re-check on a schedule, so this call is impatience rather than obligation.
curl -X POST "https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/verify" \
-H "Authorization: Bearer $RASKET_API_KEY" \
-H "User-Agent: acme-billing/1.0"const response = await fetch("https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/verify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
"User-Agent": "acme-billing/1.0",
},
});
const { id } = await response.json();import os
import requests
response = requests.post(
"https://api.rasket.com/domains/d91a7b60-1a5f-4a2e-9d1b-0d9f2c7a1e34/verify",
headers={
"Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
"User-Agent": "acme-billing/1.0",
},
)
id = response.json()["id"]When status reads verified, you can send. Nothing goes out before that.
5. Send
curl -X POST "https://api.rasket.com/emails" \
-H "Authorization: Bearer $RASKET_API_KEY" \
-H "User-Agent: acme-billing/1.0" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{
"from": "Acme <orders@send.acme.example>",
"to": ["ronald.williams@example.com"],
"subject": "Your order has shipped",
"html": "<p>Order 1042 left the warehouse this morning.</p>"
}'const response = await fetch("https://api.rasket.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
"User-Agent": "acme-billing/1.0",
"Content-Type": "application/json",
"Idempotency-Key": "order-1042",
},
body: JSON.stringify({
from: "Acme <orders@send.acme.example>",
to: ["ronald.williams@example.com"],
subject: "Your order has shipped",
html: "<p>Order 1042 left the warehouse this morning.</p>"
}),
});
const { id } = await response.json();import os
import requests
response = requests.post(
"https://api.rasket.com/emails",
headers={
"Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
"User-Agent": "acme-billing/1.0",
"Idempotency-Key": "order-1042",
},
json={
"from": "Acme <orders@send.acme.example>",
"to": ["ronald.williams@example.com"],
"subject": "Your order has shipped",
"html": "<p>Order 1042 left the warehouse this morning.</p>"
},
)
id = response.json()["id"]What the response means
A 200 with an id means we have accepted your message and taken responsibility for it. It does not mean it has been delivered — that comes back as an event, seconds to minutes later.
Then look
curl -X GET "https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c" \
-H "Authorization: Bearer $RASKET_API_KEY" \
-H "User-Agent: acme-billing/1.0"const response = await fetch("https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.RASKET_API_KEY}`,
"User-Agent": "acme-billing/1.0",
},
});
const { id } = await response.json();import os
import requests
response = requests.get(
"https://api.rasket.com/emails/4ef9a417-02e9-4d39-ad75-9611e0fcc33c",
headers={
"Authorization": f"Bearer {os.environ['RASKET_API_KEY']}",
"User-Agent": "acme-billing/1.0",
},
)
id = response.json()["id"]last_event is the furthest state the message has reached. For anything more than a spot check, subscribe a webhook rather than polling.
Next
- Add an idempotency key so a retry cannot send twice.
- Read the error vocabulary once, so your handler knows what to retry and what to fix.
- Subscribe a webhook and verify its signature, then read the events it will send you.