# Manage subscribers

Create, update, unsubscribe and delete email subscribers by address from your backend with an org API key.

Keep your email audience in step with your app: add a subscriber when someone signs
up, tag them as their plan changes, and unsubscribe them when they ask. Subscribers
are addressed by email and are the same contacts you see in the console audience.

> **Note:**
  Create an API role under **Settings > API roles** with `audience` (read,
  create, update, delete), plus `audience_lists` (update) to add people to
  lists. Then an API key with that role under **Settings > API keys**. Tags are
  created in the console.

The `subscribers` calls ship in `@buildbase/sdk` 1.0.0. If `bb.subscribers` is `undefined`, you are on an earlier version.

## Quick example

```typescript title="lib/audience.ts"
import { createServerClient } from '@buildbase/sdk/server';

const bb = createServerClient({
  serverUrl: 'https://api.console.buildbase.app',
  apiToken: process.env.BUILDBASE_API_TOKEN!, // <orgId>:<secret>
});

export async function onSignup(user: {
  email: string;
  name: string;
  plan: string;
}) {
  const subscriber = await bb.subscribers.upsert({
    email: user.email,
    name: user.name,
    tags: ['customer'],
    attributes: { plan: user.plan },
    lists: [process.env.NEWSLETTER_LIST_ID!],
  });
  return subscriber.created; // true the first time, false after
}
```

## Upsert

`upsert` creates the subscriber, or updates the one with that address. What you leave
out is kept.

| Field                                         | Type                                                  | Description                                               |
| --------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------- |
| `email`                                       | `string`                                              | Required. Case does not matter                            |
| `name`                                        | `string`                                              | Display name; a new subscriber without one uses the email |
| `tags`                                        | `string[]`                                            | Tag slugs to add, up to 50                                |
| `removeTags`                                  | `string[]`                                            | Tag slugs to remove                                       |
| `attributes`                                  | `Record<string, string \| number \| boolean \| null>` | Merged over the current attributes, up to 100 keys        |
| `lists`                                       | `string[]`                                            | Static list ids to add them to                            |
| `timezone`, `language`, `country`, `currency` | `string`                                              | Profile fields                                            |

An upsert **never changes whether someone is subscribed**. A sign-up form that
upserts again does not resubscribe a person who left.

An unknown tag slug or list id is refused with `UNKNOWN_REFERENCE` and changes nothing.
A new subscriber counts toward your plan's contact limit; updating one never does.

## Read, unsubscribe and resubscribe

```typescript title="lib/audience.ts"
const subscriber = await bb.subscribers.get('ada@example.com'); // null when there is none

await bb.subscribers.unsubscribe('ada@example.com', {
  reason: 'asked by email',
});
await bb.subscribers.resubscribe('ada@example.com');
```

`unsubscribe` stops marketing and transactional sends to the address; account email
such as password resets still arrives. `resubscribe` is refused with
`RECIPIENT_SUPPRESSED` for an address that bounced or is a spam trap.

## Delete

```typescript title="lib/audience.ts"
await bb.subscribers.delete('ada@example.com');
```

Delete takes the subscriber off every list (firing `audience.removed_from_list` for
each) and then soft-deletes them: they no longer appear in the console, in reads
or in any send, but the record is kept, marked deleted, and so is their history,
such as the email already sent to them, so a delete is not an erasure of their
data. The console has no delete; this call is
the only way to remove a contact. The address can be added again later and then
starts subscribed, so to stop email to a person who asked, unsubscribe them
instead.

## Events

Every change fires the same events as the console, so your workflows and
[webhooks](/webhooks/overview) see it: `audience.member_created`,
`audience.member_updated`, `audience.tag_added`, `audience.tag_removed`,
`audience.attribute_changed`, `audience.added_to_list`, `audience.removed_from_list`
(on delete), `audience.unsubscribed` and `audience.resubscribed`.

## Handling refusals

Refusals throw `BuildBaseApiError` with `status` and `code`:

| Code                   | Status | Meaning                                                |
| ---------------------- | ------ | ------------------------------------------------------ |
| `UNKNOWN_REFERENCE`    | 422    | A tag slug or list id that does not exist; see `field` |
| `RECIPIENT_SUPPRESSED` | 422    | `resubscribe` for an address that bounced              |
| `SUBSCRIBER_NOT_FOUND` | 404    | No subscriber with that address                        |
| `API_KEY_REQUIRED`     | 403    | Called with a console session instead of an API key    |

The contact or list-size limit is a `402` with the plan's code.

## Next steps

- [Send transactional email](/server-sdk/sending-email)
- [Email campaigns](/email/overview)
- [Server SDK overview](/server-sdk/overview)
