Dedicated IPs
A sending address of your own, the 17-day warmup it goes through, and the two things you can change about it.
By default your mail goes out through shared pools, which is the right answer for almost everybody: a pool carries enough steady volume to hold a reputation, and yours joins it. A dedicated address is the other trade. The reputation is entirely yours, which cuts both ways.
POST /v1/ips takes one out of inventory, answering 201 with the address. It reads {"mail_class": "transactional" | "marketing"} and an optional region, and the class travels with the address for life: an address that spent three weeks warming on newsletters carries a newsletter reputation, so the two never share one. Warmup starts immediately and cannot be skipped. It answers 402 plan_required on the free plan, and 503 no_address_available when inventory is empty, which is a retry rather than a mistake. DELETE /v1/ips/:id hands the address back; sends fall back to the shared pool for their class immediately, so releasing one does not stop mail, but the warmup it had accumulated stops being yours. Pause while you are investigating a reputation problem, release when you are finished with it.
What you have
curl https://emails.sh/v1/ips \
-H "Authorization: Bearer $EMAILSSH_API_KEY"{
"default_region": "eu",
"residency": { "sending": "eu-west-1", "storage": "eu", "compute": "eu" },
"ips": [
{
"id": "5c1a7e04-8b39-4f62-a0d7-1e94b3c76085",
"ip": "203.0.113.42",
"region": "eu",
"region_label": "Europe (Ireland)",
"mail_class": "marketing",
"pool_name": "acme-marketing",
"warmup_state": "warming",
"warmup_day": 6,
"daily_cap": 10000,
"sent_today": 4102,
"day": "2026-07-28",
"assigned_at": "2026-07-22T09:00:00.000Z",
"warmup_started_at": "2026-07-23T00:00:00.000Z",
"warmup_completed_at": null
}
],
"shared_pools": [
{ "mail_class": "transactional", "label": "Transactional", "pool_name": "shared-tx-eu" },
{ "mail_class": "marketing", "label": "Marketing", "pool_name": "shared-mkt-eu" }
]
}mail_class is derived from the pool name rather than set separately, and it is transactional, marketing, or null. Keeping the two classes on separate addresses is the whole reason to have more than one: a campaign that gets complaints then cannot damage the deliverability of your password resets.
Warmup
A new address has no history, and an address with no history that sends a hundred thousand messages on its first day looks exactly like a spammer, because usually it is one. So a new address ramps, with a cap that rises each day for 17 days.
| Day | Daily cap |
|---|---|
1 | 50 |
2 | 100 |
3 | 500 |
4 | 1000 |
5 | 5000 |
6 | 10000 |
7 | 20000 |
8 | 40000 |
9 | 70000 |
10 | 100000 |
11 | 150000 |
12 | 200000 |
13 | 300000 |
14 | 400000 |
15 | 500000 |
16 | 750000 |
17 | 1000000 |
Past day 17 the address is ready and the schedule stops applying. Overflow above the cap goes out through the shared pool rather than being refused, so warmup slows an address down without stopping your mail.
| warmup_state | What it means |
|---|---|
pending | Assigned and not started. Nothing is going through it yet. |
warming | On the schedule above. warmup_day says where. |
ready | Warmed. It carries whatever you send it. |
paused | You paused it, or we did. Mail routes through the shared pool instead. |
retired | Out of service. It refuses a PATCH. |
The two things you can change
paused and daily_cap. That is the whole of PATCH /v1/ips/:id, and anything else in the body is refused. daily_cap must be a non-negative integer or null, and it can only lower the allowance: a number above the warmup cap for the day does not raise it.
# Hold this address at 5000 a day, below whatever warmup would allow
curl -X PATCH https://emails.sh/v1/ips/5c1a7e04-8b39-4f62-a0d7-1e94b3c76085 \
-H "Authorization: Bearer $EMAILSSH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"daily_cap": 5000}'
# Take it out of rotation without giving it up
curl -X PATCH https://emails.sh/v1/ips/5c1a7e04-8b39-4f62-a0d7-1e94b3c76085 \
-H "Authorization: Bearer $EMAILSSH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"paused": true}'Reverse DNS
GET on one address runs a live PTR check and tells you what the record should say, what it does say, whether a forward lookup confirms it, and whether the whole thing is in order. A dedicated address whose reverse DNS does not forward-confirm is one several large receivers treat with suspicion, so this is the field to watch after provisioning.
{
"id": "5c1a7e04-8b39-4f62-a0d7-1e94b3c76085",
"ip": "203.0.113.42",
"reverse_dns": {
"ip": "203.0.113.42",
"expected": "a42.out.emails.sh",
"found": "a42.out.emails.sh",
"forwardConfirmed": true,
"ok": true
}
}Do you want one
- Under a few hundred thousand a month, probably not
- A dedicated address needs steady volume to hold a reputation. Send too little and receivers have nothing recent to judge it by, which is worse than being in a healthy pool.
- Separating marketing from transactional, yes
- This is the strongest reason. A campaign and a password reset should not share a reputation, and separate addresses are how that is enforced rather than hoped for.
- To escape a bad shared reputation, no
- A dedicated address starts with no reputation at all, and 17 days of warmup, and every complaint after that is yours alone. If your mail is getting filtered, fix the mail.
- A compliance requirement, sometimes
- Some contracts specify a dedicated sending address. That is a real reason and it is fine.
/v1/ipsDedicated addresses, shared pools, the default region, and data residency.
/v1/ips/:idOne address, with a live reverse DNS check.
/v1/ips/:id{ paused?, daily_cap? } and nothing else.
ips.list
Dedicated addresses on the workspace, the shared pools anything else goes through, the default region, and where sending, storage, and compute physically happen.
Takes no arguments.
Returns{ default_region, residency, ips[], shared_pools[] }
ips.get
One address, plus a live reverse DNS check: what the PTR should say, what it says, and whether the forward lookup confirms it.
Arguments
idstringrequired
ReturnsIp & { reverse_dns }
ips.update
The only two things about an address you can change. POST /v1/ips takes one out of inventory and DELETE /v1/ips/:id hands it back; this route is for pausing one and lowering its cap.
Arguments
idstringrequiredpausedboolean- Stop routing mail through it without giving it up.
daily_capnumber | null- A non-negative integer, or null for no cap of your own. It can lower the warmup allowance and never raise it.
ReturnsIp