# Porkbun API v3 — Email

> Email forwarding (forward an address at the domain to another mailbox) and email hosting (mailboxes: buy, set up, passwords)

Part of the Porkbun API v3.64. Topic index: https://porkbun.com/llms · Full reference: https://porkbun.com/llms-full.txt · Overview: https://porkbun.com/llms.txt · OpenAPI spec: https://porkbun.com/api/json/v3/spec

**Auth:** send `X-API-Key` / `X-Secret-API-Key` headers (preferred) or `apikey` / `secretapikey` in the JSON body. Create keys at https://porkbun.com/account/api

---

# Endpoints

## POST /api/json/v3/email/getForwards/{domain}

**List email forwards**

The email forwards on a domain: each `address` at the domain and the `forwardTo` mailbox it is delivered to (an address can forward to several). `limits` gives how many the domain has and may have; `dnsConfigured` says whether the apex MX records in the Porkbun zone point at Porkbun forwarding (if the domain uses other nameservers, those decide). Supports GET with header auth.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/getForwards/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `forwards` | object[] |  |
| `limits` | object | Forwards the domain has (`used`, including any Porkbun manages itself) and may have (`max`) |
| `dnsConfigured` | boolean |  |

## GET /api/json/v3/email/getForwards/{domain}

**List email forwards**

The email forwards on a domain: each `address` at the domain and the `forwardTo` mailbox it is delivered to (an address can forward to several). `limits` gives how many the domain has and may have; `dnsConfigured` says whether the apex MX records in the Porkbun zone point at Porkbun forwarding (if the domain uses other nameservers, those decide). Supports GET with header auth.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/getForwards/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `forwards` | object[] |  |
| `limits` | object | Forwards the domain has (`used`, including any Porkbun manages itself) and may have (`max`) |
| `dnsConfigured` | boolean |  |

## POST /api/json/v3/email/addForward/{domain}

**Add an email forward**

Forward an address at the domain to another mailbox. Free, up to the domain's limit (20 by default). Forwarding needs Porkbun's MX records and SPF include at the zone apex, and this sets them up. **When that would delete other apex MX records (another mail service) or rewrite an existing SPF record, the call is refused with `DNS_CHANGE_CONFIRMATION_REQUIRED` and `dnsChanges` describing exactly what would change; tell the user, then resend with `confirmDnsChanges: true`.** `dryRun: true` previews it, including whether confirmation is needed. Catch-all/wildcard forwards are not supported. An address cannot be both a forward and an email hosting mailbox.

**Key permission needed:** `email`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | The address at the domain to forward: the part before the @ (`info`) or the whole address (`info@example.com`) |
| `forwardTo` | string | yes | The mailbox to deliver to |
| `confirmDnsChanges` | boolean | no | Agree to the DNS changes a DNS_CHANGE_CONFIRMATION_REQUIRED response described |
| `dryRun` | boolean | no |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/addForward/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","address":"info","forwardTo":"me@example.net"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `address` | string |  |
| `forwardTo` | string |  |
| `dnsChanges` | object | What setting up forwarding does to the zone apex |
| `limits` | object | Forwards the domain has (`used`, including any Porkbun manages itself) and may have (`max`) |

## POST /api/json/v3/email/deleteForward/{domain}

**Delete an email forward**

Delete one forward, named by its `address` and `forwardTo` (an address can forward to several). When it was the domain's last forward and there are no email hosting mailboxes, Porkbun's forwarding MX and SPF records are removed too (`dnsRecordsRemoved: true`).

**Key permission needed:** `email`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes |  |
| `forwardTo` | string | yes |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/deleteForward/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","address":"info","forwardTo":"me@example.net"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `address` | string |  |
| `forwardTo` | string |  |
| `dnsRecordsRemoved` | boolean |  |

## POST /api/json/v3/email/getMailboxes/{domain}

**List mailboxes**

The domain's email hosting mailboxes, including ones bought or included as a trial that are still waiting to be set up (`status: PENDINGSETUP`, `address: null`). Also returns the price of another mailbox and the mail client settings. Supports GET with header auth.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/getMailboxes/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `mailboxes` | object[] |  |
| `price` | object |  |
| `clientSettings` | object | Mail client settings: IMAP imap.porkbun.com:993 SSL/TLS, POP pop.porkbun.com:995, SMTP smtp.porkbun.com:587 STARTTLS (or 465 implicit TLS, 50587), webmail https://webmail.porkbun.com; the username is the full address |

## GET /api/json/v3/email/getMailboxes/{domain}

**List mailboxes**

The domain's email hosting mailboxes, including ones bought or included as a trial that are still waiting to be set up (`status: PENDINGSETUP`, `address: null`). Also returns the price of another mailbox and the mail client settings. Supports GET with header auth.

**Key permission needed:** `read`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/getMailboxes/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_..."}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `mailboxes` | object[] |  |
| `price` | object |  |
| `clientSettings` | object | Mail client settings: IMAP imap.porkbun.com:993 SSL/TLS, POP pop.porkbun.com:995, SMTP smtp.porkbun.com:587 STARTTLS (or 465 implicit TLS, 50587), webmail https://webmail.porkbun.com; the username is the full address |

## POST /api/json/v3/email/setupMailbox/{domain}

**Set up a waiting mailbox**

Give a mailbox that is already paid for, or the free trial mailbox that comes with a domain registration, its address and password. Free. `mailboxId` picks one; without it the first waiting mailbox is used (`NO_PENDING_MAILBOX` if there is none: buy one with /email/createMailbox). Without `password`, a strong one is generated and returned once in `password`. Puts Porkbun's MX and SPF in place, asking first (`DNS_CHANGE_CONFIRMATION_REQUIRED`) if that would replace another mail service's MX or rewrite the SPF record. Needs a verified email, and a verified phone or an approved ID check. DKIM and DMARC are set up on the website.

**Key permission needed:** `email`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | The part before the @ (`me`) or the whole address. 1-30 characters: letters, digits and + . - _ |
| `password` | string | no | Optional. 12-72 bytes with an upper case letter, a lower case letter, a number and a special character. Leave out to have a strong one generated and returned once |
| `confirmDnsChanges` | boolean | no | Agree to DNS changes a DNS_CHANGE_CONFIRMATION_REQUIRED response described |
| `dryRun` | boolean | no |  |
| `mailboxId` | integer | no | Which waiting mailbox (from /email/getMailboxes) |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/setupMailbox/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","address":"me"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `mailbox` | object |  |
| `password` | string | Only when the password was generated (no `password` in the request). Shown once; give it to the user |
| `passwordGenerated` | boolean |  |
| `dnsChanges` | object | Same shape as /email/addForward's dnsChanges: mxRecordsRemoved and spf |
| `clientSettings` | object | Mail client settings: IMAP imap.porkbun.com:993 SSL/TLS, POP pop.porkbun.com:995, SMTP smtp.porkbun.com:587 STARTTLS (or 465 implicit TLS, 50587), webmail https://webmail.porkbun.com; the username is the full address |
| `orderId` | integer | createMailbox only |
| `charged` | integer | createMailbox only, cents |
| `message` | string |  |

## POST /api/json/v3/email/createMailbox/{domain}

**Buy and set up a mailbox**

Buy one email hosting mailbox ($36.00 a year, renewing automatically; the current price is in /email/getMailboxes) and set it up in the same call. `cost` must equal the price in cents (`COST_MISMATCH` names it; `dryRun: true` with `cost: 0` quotes it). Paid from account credit by default, or directly with `payWith: "card"` / `"usdc"` as for domain purchases; counts toward the monthly API spend limit. Everything else is as /email/setupMailbox: generated password unless one is sent, DNS confirmation, verification. Accounts under 90 days old that have paid in crypto cannot buy it (`EMAIL_HOSTING_UNAVAILABLE`), as on the website. If the purchase succeeds but setup fails, the response says `setupComplete: false` with the `mailboxId` to finish with /email/setupMailbox.

**Key permission needed:** `purchase`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `address` | string | yes | The part before the @ (`me`) or the whole address. 1-30 characters: letters, digits and + . - _ |
| `password` | string | no | Optional. 12-72 bytes with an upper case letter, a lower case letter, a number and a special character. Leave out to have a strong one generated and returned once |
| `confirmDnsChanges` | boolean | no | Agree to DNS changes a DNS_CHANGE_CONFIRMATION_REQUIRED response described |
| `dryRun` | boolean | no |  |
| `cost` | integer | yes | The price in cents, 3600 |
| `agreeToTerms` | string | yes |  |
| `payWith` | string | no | Pay directly instead of from account credit; see /domain/create |
| `cardPaymentId` | string | no |  |
| `usdcCheckoutId` | string | no |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/createMailbox/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","address":"me","cost":3600,"agreeToTerms":"yes"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `domain` | string |  |
| `mailbox` | object |  |
| `password` | string | Only when the password was generated (no `password` in the request). Shown once; give it to the user |
| `passwordGenerated` | boolean |  |
| `dnsChanges` | object | Same shape as /email/addForward's dnsChanges: mxRecordsRemoved and spf |
| `clientSettings` | object | Mail client settings: IMAP imap.porkbun.com:993 SSL/TLS, POP pop.porkbun.com:995, SMTP smtp.porkbun.com:587 STARTTLS (or 465 implicit TLS, 50587), webmail https://webmail.porkbun.com; the username is the full address |
| `orderId` | integer | createMailbox only |
| `charged` | integer | createMailbox only, cents |
| `message` | string |  |

## POST /api/json/v3/email/renewMailbox/{domain}

**Renew a mailbox for a year**

Add a year to a mailbox now, at its renewal price (`renewalPrice` in /email/getMailboxes; $36.00, or the legacy price for mailboxes that still qualify). A set-up mailbox also renews on its own when it is due (`autoRenews`), charged like any other renewal; this is for paying early, or for converting a trial to paid. Name the mailbox with `mailboxId` or `address`. `cost` must equal the price (`dryRun: true` with `cost: 0` quotes it and returns `expiresAfter`). Paid from account credit or with `payWith: "card"` / `"usdc"`, within the monthly API spend limit. The year is added to the current expiry date. While the domain is expired the mailbox can't be renewed, by this call or automatically: renew the domain first (`DOMAIN_EXPIRED`).

**Key permission needed:** `purchase`

| Parameter | In | Required | Description |
|---|---|---|---|
| `domain` | path | yes |  |
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `mailboxId` | integer | no |  |
| `address` | string | no | The mailbox address, or the part before the @ |
| `cost` | integer | yes |  |
| `agreeToTerms` | string | yes |  |
| `payWith` | string | no |  |
| `cardPaymentId` | string | no |  |
| `usdcCheckoutId` | string | no |  |
| `dryRun` | boolean | no |  |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/renewMailbox/example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","address":"me","cost":3600,"agreeToTerms":"yes"}'
```

Response fields:

| Field | Type | Description |
|---|---|---|
| `status` | string |  |
| `mailbox` | object |  |
| `orderId` | integer |  |
| `charged` | integer |  |
| `message` | string |  |

## POST /api/json/v3/email/setPassword

**Set email hosting password**

Set the password for an email hosting account associated with a domain managed by your API key.

**Key permission needed:** `email`

| Parameter | In | Required | Description |
|---|---|---|---|
| `Idempotency-Key` | header | no | Optional, recommended on every purchase and write. Send a unique value (a UUID) per intended operation. Repeating the request with the same key and an identical body within 24 hours returns the original response instead of doing it again, so a retry after a timeout cannot double-charge or double-apply. The same key with a different body returns 409 IDEMPOTENCY_KEY_MISMATCH; while the first request is still running, 409 IDEMPOTENCY_KEY_IN_USE. |

Request body fields:

| Field | Type | Required | Description |
|---|---|---|---|
| `emailAddress` | string | yes | The full email address (e.g. user@example.com) |
| `password` | string | yes | The new password. Must pass Porkbun password validation rules. |

```bash
curl -X POST https://api.porkbun.com/api/json/v3/email/setPassword \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","emailAddress":"user@example.com"}'
```

Response fields (BasicResponse):

| Field | Type | Description |
|---|---|---|
| `warnings` | string[] | Advisory, and present only when there is something to say. It never means the call failed. The one to handle: a DNS write is accepted and stored even when the domain is NOT delegated to our nameservers -- we keep the zone ready in case the delegation comes back -- so the write changed nothing that resolves, and this field says so. Show these to the user as written. |
| `status` | string |  |
| `message` | string | Human-readable message. Present on ERROR, sometimes on SUCCESS. |
| `code` | string | Machine-readable error code. Present when status is ERROR. |

---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- Topic index: https://porkbun.com/llms
- Full reference (one file): https://porkbun.com/llms-full.txt
- OpenAPI spec (full schemas): https://porkbun.com/api/json/v3/spec
- Short overview: https://porkbun.com/llms.txt
- Official MCP server: https://porkbun.com/mcp (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
