---
title: "Configuration"
description: "Every option of the client, what it reads from the environment, and what it refuses."
url: "https://openemail.uk/docs/csharp/configuration"
area: "C#"
category: "Getting started"
---

# Configuration

Every option of the client, what it reads from the environment, and what it refuses.

## Options

**client.cs**

```
using var http = new HttpClient();

var client = new OpenEmailClient(new OpenEmailOptions
{
    ApiKey = Environment.GetEnvironmentVariable("OPENEMAIL_API_KEY"),
    BaseUrl = "https://api.openemail.uk",
    Timeout = TimeSpan.FromSeconds(30),
    MaxRetries = 2,
    HttpClient = http,
    Headers = { ["X-Team"] = "billing" },
});

Console.WriteLine($"{client.Mode} {client.Raw.BaseUrl}");
```

| Option | What it does |
| --- | --- |
| `ApiKey` | The workspace API key, beginning `oe_live_` or `oe_test_`. |
| `AccessToken` | An OAuth access token, for an app a person connected. Pass a key or a token, not both. |
| `AccessTokenProvider` | A function that returns the access token. It runs before every request, so it is the place to renew a token. |
| `BaseUrl` | The API origin. A bare host such as `localhost:2222` gets its scheme added. |
| `HttpClient` | The `HttpClient` your application already has, such as one from `IHttpClientFactory` or one with a test handler. The client never disposes it. |
| `Timeout` | How long one attempt may take. The default is 30 seconds, `TimeSpan.Zero` turns it off, and uploads wait at least ten minutes. |
| `MaxRetries` | How many times a failed request is tried again. The default is 2, and 0 sends once. |
| `UserAgent` | Replaces the `User-Agent` header, which is `openemail-dotnet/` and the version. |
| `Headers` | Headers added to every request. |

Anything you leave out is read from the environment: `OPENEMAIL_API_KEY`, then `OPENEMAIL_ACCESS_TOKEN`, and `OPENEMAIL_BASE_URL`. `client.Mode` says whether the key is a `live` or a `test` key.

## Cancellation and timeouts

Every method takes a `CancellationToken` last. Cancelling it stops the call at once, retries included, and throws `OperationCanceledException`, while `Timeout` bounds each attempt.

**deadline.cs**

```
using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(10));

var thread = await client.Threads.GetAsync("CAHk7pQ2x9LmZ4-mail.example.com", cancellationToken: deadline.Token);

Console.WriteLine(thread["messageCount"]);
```

## Options on a call

Query parameters and call settings are named arguments under the names of the API, such as `limit:`, `cursor:` and `idempotencyKey:`. Every method takes `apiKey:` to act with another key for that call only, so one process can serve several workspaces with one client.

**another-workspace.cs**

```
var page = await client.Emails.ListAsync(
    status: new[] { "failed", "bounced" },
    limit: 50,
    apiKey: Environment.GetEnvironmentVariable("OTHER_WORKSPACE_KEY"));

Console.WriteLine($"{page.Count} {page.HasMore}");
```

## OAuth access tokens

An app a person connected with OAuth holds an access token instead of an API key. Pass it as `AccessToken`, or pass `AccessTokenProvider` and renew the token there when it is close to expiring, so the client never has to be rebuilt.

**access-token.cs**

```
var current = "token-from-your-oauth-flow";

var client = new OpenEmailClient(new OpenEmailOptions
{
    AccessTokenProvider = _ => Task.FromResult(current),
});

var me = await client.Me.GetAsync();

Console.WriteLine(me["workspaceId"]);
```

## ASP.NET Core

Register one client for the whole application. It holds no connection of its own to dispose, so a singleton is the right lifetime.

**Program.cs**

```
using OpenEmail;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(new OpenEmailClient(new OpenEmailOptions
{
    ApiKey = builder.Configuration["OpenEmail:ApiKey"],
}));

var app = builder.Build();

app.MapPost("/invoices/{number}/send", async (string number, OpenEmailClient client, CancellationToken cancellationToken) =>
{
    var sent = await client.Emails.SendAsync(new Body
    {
        ["from"] = "billing@acme.com",
        ["to"] = "ada@example.com",
        ["subject"] = $"Invoice {number}",
        ["text"] = "The invoice is attached.",
    }, idempotencyKey: $"invoice-{number}", cancellationToken: cancellationToken);

    return Results.Ok(new { id = (string?)sent["id"] });
});

app.Run();
```

## What the client refuses

- A key that does not begin `oe_live_` or `oe_test_`, and a key together with an access token.
- Sending a credential over plain `http:`, unless the server is on this machine at `localhost`, a `127.x.x.x` address or `::1`.
- A base URL on `0.0.0.0`, which is an address a server listens on. Use `127.0.0.1` with the same port.
- An empty id, or one made only of dots, which a URL parser would remove.
- Following a redirect. The HTTP client the package builds never follows one, so a credential never travels to another host.

> Printing a client or an exception never shows a credential. `ToString()` on a client gives its mode and its base URL, and nothing else.

## An endpoint without a method

`client.Raw.RequestAsync()` calls any path with the credential, base URL, timeout and retry policy of the client. The path must begin with a single `/`, and a path that leaves the origin of the base URL is refused.

**request.cs**

```
var label = await client.Raw.RequestAsync("/labels", "POST", body: new Body { ["name"] = "Invoices" });

Console.WriteLine(label?["id"]);
```
