# Connect an SMTP/IMAP mailbox

Source: https://docs.emelia.io/docs/email-providers/connect-mailbox

Guide: [Email providers](https://docs.emelia.io/docs/email-providers.md)

`POST https://api.emelia.io/email-providers`

Adds a mailbox after testing its SMTP and IMAP credentials. The number of mailboxes is limited by your subscription (1 plus the additional mailboxes you pay for).

## Authentication

Send your API key in the `Authorization` header: `Authorization: <your API key>`.

## Request body

Content type: `application/json`, required.

SMTP and IMAP credentials of the mailbox. The credentials are tested before saving. For Google and Microsoft accounts prefer the OAuth connection in the app.

| Field | Type | Required | Description |
|---|---|---|---|
| `senderName` | string | no |  |
| `senderEmail` | string (email) | yes |  |
| `senderPassword` | string | no | Used when `smtp.password` is empty. |
| `emailType` | enum: `GOOGLE`, `OFFICE`, `EXCHANGE`, `SMTP`, `GOOGLEIMAP` | yes |  |
| `smtp` | object | no |  |
| `smtp.login` | string | no |  |
| `smtp.password` | string | no |  |
| `smtp.server` | string | yes |  |
| `smtp.port` | integer | yes |  |
| `smtp.ssl` | boolean | no |  |
| `imap` | object | no |  |
| `imap.login` | string | no |  |
| `imap.password` | string | no |  |
| `imap.server` | string | yes |  |
| `imap.port` | integer | yes |  |
| `imap.ssl` | boolean | no |  |
| `signature` | string | no |  |
| `domainName` | string | no | Optional custom tracking domain to attach. |

```json
{
  "senderName": "Marie Martin",
  "senderEmail": "marie@acme.com",
  "emailType": "SMTP",
  "smtp": {
    "login": "marie@acme.com",
    "password": "app-password",
    "server": "smtp.acme.com",
    "port": 465,
    "ssl": true
  },
  "imap": {
    "login": "marie@acme.com",
    "password": "app-password",
    "server": "imap.acme.com",
    "port": 993,
    "ssl": true
  }
}
```

## Example request

**cURL**

```bash
curl -X POST "https://api.emelia.io/email-providers" \
  -H "Authorization: $EMELIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "senderName": "Marie Martin",
  "senderEmail": "marie@acme.com",
  "emailType": "SMTP",
  "smtp": {
    "login": "marie@acme.com",
    "password": "app-password",
    "server": "smtp.acme.com",
    "port": 465,
    "ssl": true
  },
  "imap": {
    "login": "marie@acme.com",
    "password": "app-password",
    "server": "imap.acme.com",
    "port": 993,
    "ssl": true
  }
}'
```

**JavaScript**

```js
const response = await fetch('https://api.emelia.io/email-providers', {
  method: 'POST',
  headers: { Authorization: process.env.EMELIA_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "senderName": "Marie Martin",
    "senderEmail": "marie@acme.com",
    "emailType": "SMTP",
    "smtp": {
      "login": "marie@acme.com",
      "password": "app-password",
      "server": "smtp.acme.com",
      "port": 465,
      "ssl": true
    },
    "imap": {
      "login": "marie@acme.com",
      "password": "app-password",
      "server": "imap.acme.com",
      "port": 993,
      "ssl": true
    }
  }),
});
console.log(await response.json());
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.emelia.io/email-providers",
    headers={"Authorization": os.environ["EMELIA_API_KEY"]},
    json={
        "senderName": "Marie Martin",
        "senderEmail": "marie@acme.com",
        "emailType": "SMTP",
        "smtp": {
            "login": "marie@acme.com",
            "password": "app-password",
            "server": "smtp.acme.com",
            "port": 465,
            "ssl": True
        },
        "imap": {
            "login": "marie@acme.com",
            "password": "app-password",
            "server": "imap.acme.com",
            "port": 993,
            "ssl": True
        }
    },
)
print(response.json())
```

## Responses

### 200: Success.

| Field | Type | Required | Description |
|---|---|---|---|
| `success` | any | no |  |
| `id` | string | no | MongoDB ObjectId: 24 hexadecimal characters. |

```json
{
  "success": true,
  "id": "6650f1a2b839d80011c5a0aa"
}
```

### 400: Invalid request or credentials rejected.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

```json
{
  "error": "string",
  "code": "INVALID_REQUEST",
  "params": {}
}
```

### 401: Missing, malformed, unknown or disabled API key. The key must be exactly 48 characters, otherwise it is treated as a session token and rejected the same way.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

```json
{
  "error": "Authentication required"
}
```

### 500: Unexpected error. IMPORTANT: this is also what you receive today when the per minute quota of your account is exceeded (the quota error is not mapped to 429 for API keys). No `Retry-After` header is sent. Wait until the next minute window before retrying.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | string or object | yes | Human readable message. A few legacy routes return an empty object `{}` here instead of a string. |
| `code` | enum: `INVALID_REQUEST`, `INVALID_PARAMETERS`, `SUBSCRIPTION_REQUIRED`, `CAMPAIGN_NOT_FOUND`, `CAMPAIGN_ALREADY_RUNNING`, `SCHEDULE_MISSING`, `STEPS_MISSING`, `NO_RECIPIENTS`, `TOO_MANY_RECIPIENTS`, `PLANNED_START_INVALID`, `PLANNED_START_IN_PAST`, `EMAIL_PROVIDER_MISSING`, `EMAIL_PROVIDER_DISCONNECTED`, `EMAIL_PROVIDER_EXPIRED`, `ONE_EMAIL_PROVIDER_EXPIRED`, `EMAIL_ADDRESS_DISCONNECTED`, `TRACKING_DOMAINS_REQUIRED`, `LINKEDIN_ACCOUNT_MISSING`, `LINKEDIN_COOKIE_EXPIRED`, `LINKEDIN_SLOT_EXPIRED`, `WHATSAPP_ACCOUNT_EXPIRED`, `WHATSAPP_SLOT_EXPIRED`, `NO_IDENTITY_SET`, `IDENTITY_FOREIGN_ACCOUNT`, `IDENTITY_EMAIL_PROVIDER_MISSING`, `LINKEDIN_ACCOUNT_AMBIGUOUS`, `WHATSAPP_ACCOUNT_AMBIGUOUS`, `LINKEDIN_AUDIO_MISSING`, `EMAIL_STEP_NO_ACTIVE_VERSION`, `EMAIL_VERSION_NO_CONTENT`, `EMAIL_VERSION_NO_SUBJECT`, `RECIPIENTS_NO_EMAIL`, `RECIPIENTS_NO_LINKEDIN`, `MC_LINKEDIN_DAILY_LIMIT_CONNECTION`, `MC_LINKEDIN_DAILY_LIMIT`, `DAILY_CONTACT_TOO_LOW`, `LINKEDIN_DAILY_CONTACT_LIMIT`, `VERIFY_EMAIL_TOKEN_INVALID`, `VERIFY_EMAIL_TOKEN_EXPIRED`, `FREE_CREDITS_DOMAIN_ALREADY_CLAIMED`, `FREE_CREDITS_ALREADY_GRANTED`, `FREE_CREDITS_NOT_ELIGIBLE`, `RATE_LIMITED`, `STATS_COMPUTING`, `WARMUP_PROVIDER_UNSUPPORTED`, `ACCOUNT_DISCONNECTED` | no | Stable machine readable error code. Present only on catalogued errors (mainly the campaign start checks), on validation errors (`INVALID_REQUEST`) and on some LinkedIn provider errors (`ACCOUNT_DISCONNECTED`, `RATE_LIMITED`). Most other errors have no code: rely on the HTTP status and on `error`. Codes are never renamed once deployed. |
| `params` | object | no | Values interpolated in the message, for example `{"max": 30000}`. |

```json
{
  "error": "Internal server error"
}
```
