guides/recipes/custom-adapter.md

# Custom Adapter Recipe

This guide walks you through implementing a custom outbound channel adapter using the `Chimeway.Adapter` behaviour. It covers required callbacks, secure runtime configuration, returning delivery outcomes, and writing contract tests to enforce redaction.

## Implementing the Behaviour

A custom adapter in Chimeway is any module that implements the `Chimeway.Adapter` behaviour. The primary responsibility of an adapter is to take a pre-planned `%Chimeway.Delivery{}` struct and dispatch it to an external provider (like an SMS gateway, email service, or push notification API).

Here is a basic structure for a custom adapter:

```elixir
defmodule MyApp.MyCustomAdapter do
  @behaviour Chimeway.Adapter

  @impl true
  def deliver(%Chimeway.Delivery{} = delivery, config) do
    # Implementation goes here
  end
end
```

## Secure Runtime Configuration

**CRITICAL:** Adapter configuration (such as API keys, base URLs, or secrets) MUST be loaded at runtime. 

Do not use compile-time module attributes (e.g., `@api_key Application.compile_env(...)`). Instead, you must read configuration at call time, typically via `Application.get_env/3` or through the `config` keyword list passed to your `deliver/2` function. 

Loading configuration at runtime ensures multi-environment safety and allows you to seamlessly switch environments or override configurations during tests without needing to recompile your application.

```elixir
defmodule MyApp.MyCustomAdapter do
  @behaviour Chimeway.Adapter

  @impl true
  def deliver(%Chimeway.Delivery{} = delivery, config) do
    # Good: Reading config at runtime
    api_key = Keyword.get(config, :api_key) || Application.get_env(:my_app, :custom_adapter_api_key)
    
    # ... external API call ...
  end
end
```

## Return Shapes and Outcomes

The `deliver/2` function must return one of the following specific shapes to allow Chimeway's dispatcher to properly handle the outcome:

### Success
- `{:ok, meta}`: Indicates the provider accepted the delivery. 
  - `meta` is a map containing relevant provider response details (like an external message ID). It will be persisted to `chimeway_delivery_attempts.provider_response`. 
  - **Security Note:** You MUST redact sensitive fields (like passwords, tokens, secrets, API keys, or auth headers) from `meta` before returning it.

### Error
- `{:error, class, detail}`: Indicates the delivery failed.
  - `class` MUST be one of `:temporary`, `:permanent`, or `:bounced`:
    - `:temporary` - A transient failure (e.g., 500 Server Error). The dispatcher may retry.
    - `:permanent` - A non-retriable rejection (e.g., 400 Bad Request).
    - `:bounced` - The address or identity is unreachable.
  - `detail` is a map containing error specifics. Like `meta`, it must not contain any sensitive data, PII, or full provider response bodies.

## Enforcing Safety with Contract Tests

To guarantee that your custom adapter correctly implements the behaviour and adheres to security requirements (specifically, redaction of credentials in metadata), Chimeway provides a shared contract test suite.

You should use the `Chimeway.Adapter.ContractTest` macro in your test suite to automatically run these checks against your adapter. This enforces runtime safety and prevents accidental credential leaks.

```elixir
defmodule MyApp.MyCustomAdapterTest do
  use ExUnit.Case, async: true
  
  # This macro injects shared contract tests for your adapter
  use Chimeway.Adapter.ContractTest, adapter: MyApp.MyCustomAdapter

  # ... your custom tests ...
end
```

Using this macro is the recommended way to ensure your adapter meets Chimeway's production-grade standards.