# Setting up MailBlastr for multi-tenant apps

> Two patterns for SaaS apps that send on behalf of tenants: a single MailBlastr account with domain-scoped keys, or separate accounts (BYOK). Trade-offs in isolation, billing, and deliverability.

Many SaaS platforms need to send email on behalf of their tenants — transactional notifications, onboarding sequences, or campaigns from a tenant’s own domain. There are two main ways to configure MailBlastr for this. Neither is universally "right"; the best choice depends on how much control you want and what your tenants need.

## At a glance

| Factor | Single account | Separate accounts / BYOK |
| --- | --- | --- |
| Setup complexity | Low — fully API-driven | Higher — manual account per tenant |
| Sending isolation | Shared — one bad actor affects all | Full — each tenant isolated |
| Billing | One plan covers all tenants | Each tenant manages their own plan |
| Per-tenant analytics | Not native | Each tenant has its own dashboard |
| Webhook routing | Via tags or `from` domain | Each account has its own webhooks |
| Deliverability | Shared sender reputation | Independent sender reputation |
| Rate limits | Aggregate — likely needs an increase | Each tenant uses their own limits |

## Option A: a single MailBlastr account

You manage everything from one account: add each tenant’s domain, verify it, and mint a **domain-scoped API key** so each tenant can only send from their own domain. All sending, billing, and analytics flow through your account. The whole flow is API-driven.

When a tenant signs up, create and verify their domain.

**1. Create the tenant's domain:**

**Node.js**

```js
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.domains.create({ "name": "tenant-domain.com" });
console.log({ data, error });
```

**Ruby**

```ruby
require "mailblastr"

Mailblastr.api_key = "mb_xxxxxxxxx"

Mailblastr::Domains.create({
  "name": "tenant-domain.com"
})
```

**PHP**

```php
$mailblastr = Mailblastr::client('mb_xxxxxxxxx');

$mailblastr->domains->create([
  'name' => "tenant-domain.com"
]);
```

**Python**

```python
import mailblastr

mailblastr.api_key = "mb_xxxxxxxxx"

mailblastr.Domains.create({
  "name": "tenant-domain.com"
})
```

**Go**

```go
client := mailblastr.NewClient("mb_xxxxxxxxx")
domain, err := client.Domains.Create(&mailblastr.CreateDomainRequest{Name: "yourdomain.com"})
```

**Rust**

```rust
let mb = Mailblastr::new("mb_xxxxxxxxx");
let _domain = mb.domains.create(CreateDomainOptions::new("yourdomain.com")).await?;
```

**Java**

```java
Mailblastr mailblastr = new Mailblastr("mb_xxxxxxxxx");

CreateDomainRequest request = CreateDomainRequest.builder()
    .name("yourdomain.com")
    .build();
mailblastr.domains().create(request);
```

**.NET**

```csharp
IMailblastr mailblastr = MailblastrClient.Create("mb_xxxxxxxxx");
var resp = await mailblastr.DomainCreateAsync(new DomainCreateOptions { Name = "yourdomain.com" });
```

**cURL**

```bash
curl -X POST 'https://api.mailblastr.com/domains' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{ "name": "tenant-domain.com" }'
```

**CLI**

```bash
mailblastr domains add tenant-domain.com
```

**2. After the tenant publishes the DNS records, trigger verification:**

**Node.js**

```js
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.domains.verify('DOMAIN_ID');
console.log({ data, error });
```

**Ruby**

```ruby
require "mailblastr"

Mailblastr.api_key = "mb_xxxxxxxxx"

Mailblastr::Domains.verify("DOMAIN_ID")
```

**PHP**

```php
$mailblastr = Mailblastr::client('mb_xxxxxxxxx');

$mailblastr->domains->verify('DOMAIN_ID');
```

**Python**

```python
import mailblastr

mailblastr.api_key = "mb_xxxxxxxxx"

mailblastr.Domains.verify("DOMAIN_ID")
```

**Go**

```go
client := mailblastr.NewClient("mb_xxxxxxxxx")
verified, err := client.Domains.Verify("DOMAIN_ID")
```

**Rust**

```rust
let mb = Mailblastr::new("mb_xxxxxxxxx");
let _verified = mb.domains.verify("DOMAIN_ID").await?;
```

**Java**

```java
Mailblastr mailblastr = new Mailblastr("mb_xxxxxxxxx");
mailblastr.domains().verify("DOMAIN_ID");
```

**.NET**

```csharp
IMailblastr mailblastr = MailblastrClient.Create("mb_xxxxxxxxx");
var resp = await mailblastr.DomainVerifyAsync("DOMAIN_ID");
```

**cURL**

```bash
curl -X POST 'https://api.mailblastr.com/domains/DOMAIN_ID/verify' \
  -H 'Authorization: Bearer mb_xxxxxxxxx'
```

**CLI**

```bash
mailblastr domains verify DOMAIN_ID
```

> **Note:** Verification is asynchronous — DNS must propagate first. Listen for the [`domain.verified` webhook](https://www.mailblastr.com/docs/webhooks/domains/updated) instead of polling.

Once verified, create a `sending_access` key scoped to that domain so the tenant can only send from it:

**Node.js**

```js
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.apiKeys.create({
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
});
console.log({ data, error });
```

**Ruby**

```ruby
require "mailblastr"

Mailblastr.api_key = "mb_xxxxxxxxx"

Mailblastr::ApiKeys.create({
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
})
```

**PHP**

```php
$mailblastr = Mailblastr::client('mb_xxxxxxxxx');

$mailblastr->apiKeys->create([
  'name' => "Tenant: tenant-domain.com",
  'permission' => "sending_access",
  'domain_id' => "DOMAIN_ID"
]);
```

**Python**

```python
import mailblastr

mailblastr.api_key = "mb_xxxxxxxxx"

mailblastr.ApiKeys.create({
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
})
```

**Go**

```go
package main

import (
    "fmt"
    "io"
    "net/http"
    "strings"
)

func main() {
    req, _ := http.NewRequest("POST", "https://api.mailblastr.com/api-keys", strings.NewReader(`{
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
}`))
    req.Header.Set("Authorization", "Bearer mb_xxxxxxxxx")
    req.Header.Set("Content-Type", "application/json")
    res, _ := http.DefaultClient.Do(req)
    defer res.Body.Close()
    out, _ := io.ReadAll(res.Body)
    fmt.Println(string(out))
}
```

**Rust**

```rust
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let res = Client::new()
        .post("https://api.mailblastr.com/api-keys")
        .header("Authorization", "Bearer mb_xxxxxxxxx")
        .header("Content-Type", "application/json")
        .body(r#"{
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
}"#)
        .send()
        .await?;
    println!("{}", res.text().await?);
    Ok(())
}
```

**Java**

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.mailblastr.com/api-keys"))
    .header("Authorization", "Bearer mb_xxxxxxxxx")
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
}
"""))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

**.NET**

```csharp
using System.Net.Http;
using System.Text;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.mailblastr.com/api-keys");
request.Headers.Add("Authorization", "Bearer mb_xxxxxxxxx");
request.Content = new StringContent(@"{
  ""name"": ""Tenant: tenant-domain.com"",
  ""permission"": ""sending_access"",
  ""domain_id"": ""DOMAIN_ID""
}", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());
```

**cURL**

```bash
curl -X POST 'https://api.mailblastr.com/api-keys' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Tenant: tenant-domain.com",
  "permission": "sending_access",
  "domain_id": "DOMAIN_ID"
}'
```

**CLI**

```bash
mailblastr api-keys create \
  --name 'Tenant: tenant-domain.com' \
  --permission 'sending_access' \
  --domain-id 'DOMAIN_ID'
```

> **Warning:** The API key token is shown **only once** at creation. Store it securely — you cannot retrieve it again. See [Create API key](https://www.mailblastr.com/docs/api/api-keys-create).

- **Pros:** seamless API-driven setup; domain-scoped keys confine each tenant to their own domain; one account to manage.
- **Cons:** no per-tenant analytics in the dashboard; tenants share your sender reputation; aggregate volume will likely need a [quota increase](https://www.mailblastr.com/docs/kb/quotas).

> **Warning:** Shared reputation cuts both ways: if one tenant sends spam or runs up bounce/complaint rates, deliverability degrades for **every** tenant on your account — and in severe cases the whole account can be suspended.

## Option B: separate accounts (BYOK)

In the Bring-Your-Own-Key model, each tenant creates their own MailBlastr account, adds their domain, and gives you their API key. Your app stores each tenant’s key and uses it when sending for them:

```ts
import { MailBlastr } from 'mailblastr';

// Look up the calling tenant's own MailBlastr key, then send with it.
const tenantApiKey = await getTenantKey(tenantId);
const mb = new MailBlastr(tenantApiKey);

await mb.emails.send({
  from: 'notifications@tenant-domain.com',
  to: ['user@example.com'],
  subject: 'Your order has shipped',
  html: '<p>Your order #1234 is on its way.</p>',
});
```

- **Pros:** full isolation — each tenant’s reputation and deliverability are independent; no liability for tenant behavior on your account; each tenant has their own dashboard and rate limits.
- **Cons:** each tenant must create and manage their own account and plan, which adds onboarding friction for those unfamiliar with email infrastructure.

## Webhook routing

With **Option A**, attach a tenant identifier as a [tag](https://www.mailblastr.com/docs/api/emails-send) when sending; tags are echoed in [webhook](https://www.mailblastr.com/docs/webhooks/overview) payloads, so you can route events back to the right tenant:

**Node.js**

```js
import { MailBlastr } from 'mailblastr';

const mb = new MailBlastr('mb_xxxxxxxxx');

const { data, error } = await mb.emails.send({
  "from": "notifications@tenant-domain.com",
  "to": ["user@example.com"],
  "subject": "Welcome aboard",
  "html": "<p>Thanks for signing up.</p>",
  "tags": [{ "name": "tenant_id", "value": "tenant_abc123" }]
});
console.log({ data, error });
```

**Ruby**

```ruby
require "mailblastr"

Mailblastr.api_key = "mb_xxxxxxxxx"

Mailblastr::Emails.send({
  "from": "notifications@tenant-domain.com",
  "to": [
    "user@example.com"
  ],
  "subject": "Welcome aboard",
  "html": "<p>Thanks for signing up.</p>",
  "tags": [
    {
      "name": "tenant_id",
      "value": "tenant_abc123"
    }
  ]
})
```

**PHP**

```php
$mailblastr = Mailblastr::client('mb_xxxxxxxxx');

$mailblastr->emails->send([
  'from' => "notifications@tenant-domain.com",
  'to' => [
    "user@example.com"
  ],
  'subject' => "Welcome aboard",
  'html' => "<p>Thanks for signing up.</p>",
  'tags' => [
    [
      'name' => "tenant_id",
      'value' => "tenant_abc123"
    ]
  ]
]);
```

**Python**

```python
import mailblastr

mailblastr.api_key = "mb_xxxxxxxxx"

mailblastr.Emails.send({
  "from": "notifications@tenant-domain.com",
  "to": [
    "user@example.com"
  ],
  "subject": "Welcome aboard",
  "html": "<p>Thanks for signing up.</p>",
  "tags": [
    {
      "name": "tenant_id",
      "value": "tenant_abc123"
    }
  ]
})
```

**Go**

```go
import "github.com/shekhu10/mailblastr-sdks/mailblastr-go"

client := mailblastr.NewClient("mb_xxxxxxxxx")

params := &mailblastr.SendEmailRequest{
    From:    "Acme <hello@yourdomain.com>",
    To:      []string{"delivered@example.com"},
    Subject: "hello world",
    Html:    "<p>it works!</p>",
}
sent, err := client.Emails.Send(params)
```

**Rust**

```rust
use mailblastr::{Mailblastr, CreateEmailBaseOptions};

let mb = Mailblastr::new("mb_xxxxxxxxx");

let email = CreateEmailBaseOptions::new(
    "Acme <hello@yourdomain.com>",
    ["delivered@example.com"],
    "hello world",
).with_html("<p>it works!</p>");
let _sent = mb.emails.send(email).await?;
```

**Java**

```java
import com.mailblastr.*;

Mailblastr mailblastr = new Mailblastr("mb_xxxxxxxxx");

SendEmailRequest request = SendEmailRequest.builder()
    .from("Acme <hello@yourdomain.com>")
    .to("delivered@example.com")
    .subject("hello world")
    .html("<p>it works!</p>")
    .build();
mailblastr.emails().send(request);
```

**.NET**

```csharp
using Mailblastr;

IMailblastr mailblastr = MailblastrClient.Create("mb_xxxxxxxxx");

var resp = await mailblastr.EmailSendAsync(new EmailMessage
{
    From = "Acme <hello@yourdomain.com>",
    To = "delivered@example.com",
    Subject = "hello world",
    HtmlBody = "<p>it works!</p>",
});
```

**cURL**

```bash
curl -X POST 'https://api.mailblastr.com/emails' \
  -H 'Authorization: Bearer mb_xxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
  "from": "notifications@tenant-domain.com",
  "to": ["user@example.com"],
  "subject": "Welcome aboard",
  "html": "<p>Thanks for signing up.</p>",
  "tags": [{ "name": "tenant_id", "value": "tenant_abc123" }]
}'
```

With **Option B**, each tenant configures their own webhooks, so events are naturally isolated — no tagging needed.

## Deliverability & migration

Sender reputation is tied to the sending account and its domains. Under Option A all tenants share one reputation; under Option B each is independent. Either way, every new tenant domain should follow a warm-up schedule and ideally send from a [subdomain](https://www.mailblastr.com/docs/domains/managing) to protect the tenant’s root domain.

> **Warning:** Moving a domain between accounts requires deleting it from the original account before re-verifying it in the second — plan for a sending interruption during DNS propagation.

See [Create domain](https://www.mailblastr.com/docs/domains/managing), [Create API key](https://www.mailblastr.com/docs/api/api-keys-create), and [Webhooks](https://www.mailblastr.com/docs/webhooks/overview) for the underlying endpoints.
