> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truthlocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Email delivery & SES integration

> Production-grade email delivery using AWS Simple Email Service (SES) with bounce handling, suppression management, custom SMTP, and a complete template reference.

Production-grade email delivery using AWS Simple Email Service (SES) with bounce handling, suppression management, and observability.

## Architecture

Truthlocks uses AWS SES for all transactional emails. The integration includes:

* **SES Provider**: Direct integration with SES v2 API for sending emails
* **SNS Webhooks**: Real-time bounce/complaint notifications via SNS topics
* **Suppression List**: Automatic suppression of hard bounces and complaints
* **Environment Gating**: Log-only mode for development, SES for production

```mermaid theme={null}
graph TD
    A[Trust Registry] -->|Send| B(AWS SES)
    B --> C(Recipient)
    B -->|Notify| D(SNS Topic)
    D --> E(Webhook Handler)
    E --> F[(PostgreSQL)]
```

## AWS SES setup

### 1. Verify domain

Verify your sending domain in SES console:

```bash theme={null}
# Using AWS CLI
aws ses verify-domain-identity --domain truthlocks.com --region us-east-1

# Get DKIM tokens (add to DNS)
aws ses verify-domain-dkim --domain truthlocks.com --region us-east-1
```

### 2. Configure DNS records

Add the following records to your domain DNS:

| Type  | Name                        | Value                                   |
| :---- | :-------------------------- | :-------------------------------------- |
| TXT   | `_amazonses.truthlocks.com` | (verification token from SES)           |
| CNAME | (DKIM selector 1)           | (DKIM value from SES)                   |
| MX    | `mail.truthlocks.com`       | `feedback-smtp.us-east-1.amazonses.com` |
| TXT   | `mail.truthlocks.com`       | `v=spf1 include:amazonses.com ~all`     |

### 3. Request production access

New SES accounts are in sandbox mode. Request production access in the SES console to send emails to unverified addresses.

## Environment variables

| Variable                  | Required | Description                                                        |
| :------------------------ | :------- | :----------------------------------------------------------------- |
| `EMAIL_MODE`              | Yes      | `log` (dev) or `ses` (prod)                                        |
| `SES_REGION`              | If ses   | AWS region (e.g., `us-east-1`)                                     |
| `SES_FROM`                | If ses   | Sender address (e.g., `no-reply@truthlocks.com`)                   |
| `SES_REPLY_TO`            | No       | Reply-to address for all outgoing emails                           |
| `SES_CONFIGURATION_SET`   | No       | SES configuration set for tracking and reputation management       |
| `SES_WEBHOOK_SECRET`      | No       | Secret for validating SNS webhook signatures                       |
| `EMAIL_CHECK_SUPPRESSION` | No       | When `true`, checks the suppression list before every send attempt |

<Warning>
  In `ENV=production`, the service will fail to start if `SES_REGION` and
  `SES_FROM` are not configured.
</Warning>

## Bounce & complaint handling

SES notifications are received via an SNS webhook at `/v1/notifications/ses-events`.

### Event types

* **Bounce (Permanent)**: Hard bounce — email suppressed automatically
* **Bounce (Transient)**: Soft bounce — logged but not suppressed
* **Complaint**: User marked as spam — email suppressed automatically
* **Delivery**: Successful delivery — logged for observability

## Suppression list management

Suppressed emails are stored in the `email_suppressions` table and will not receive future emails. The API returns `EMAIL_SUPPRESSED` error code.

```sql theme={null}
-- Check if email is suppressed
SELECT * FROM email_suppressions WHERE email = 'user@example.com';

-- View recent suppressions
SELECT email, reason, suppressed_at
FROM email_suppressions
ORDER BY suppressed_at DESC
LIMIT 20;
```

## Email sender management

You can manage sender identities and custom SMTP configuration from **Settings > Email** in the tenant console. This is useful when your organization requires emails to originate from your own domain or mail infrastructure.

### Adding a sender

<Steps>
  <Step title="Open email settings">
    Navigate to **Settings > Email** in the tenant console.
  </Step>

  <Step title="Add a new sender">
    Click **Add Sender** and provide:

    * **From email** (required) — the address emails are sent from (e.g., `noreply@agency.gov`)
    * **From name** — display name shown to recipients
    * **Reply-to** — where replies are directed
  </Step>

  <Step title="Verify the sender">
    New senders start in `PENDING` status. Click **Verify** to trigger SES identity verification. Once SES confirms the domain, the sender moves to `VERIFIED` status.
  </Step>
</Steps>

### Configuring custom SMTP

Enterprise and government tenants can bypass SES entirely by attaching a custom SMTP server to a verified sender. Once configured, all emails for that tenant route through your SMTP server.

<Steps>
  <Step title="Select a verified sender">
    In **Settings > Email**, choose a sender with `VERIFIED` or `ACTIVE` status.
  </Step>

  <Step title="Configure SMTP">
    Click **Configure SMTP** and provide:

    * **SMTP host** — your mail server hostname
    * **SMTP port** — typically `587` (STARTTLS) or `465` (TLS)
    * **Username** and **Password** — SMTP authentication credentials
    * **Use TLS** — enable for encrypted connections (recommended)
  </Step>

  <Step title="Send a test email">
    After saving, send a test email to confirm delivery. The sender status changes to `ACTIVE` once the first email is sent successfully.
  </Step>
</Steps>

<Info>
  Custom SMTP is particularly useful for government tenants that require emails
  to originate from their own mail infrastructure. See the
  [B2G Procurement Pack](/packs/enterprise/b2g-procurement) for the full
  enterprise onboarding email flow.
</Info>

## Email template reference

Truthlocks uses named templates for all transactional emails. Each template supports placeholder substitution and is routed through either AWS SES or a tenant-configured custom SMTP server.

### Onboarding & invitations

| Template name                  | Trigger                                          | Description                                             | Placeholders                              |
| :----------------------------- | :----------------------------------------------- | :------------------------------------------------------ | :---------------------------------------- |
| `enterprise-onboarding-invite` | Government tenant creation or lead qualification | Branded invite for enterprise/government administrators | `{{organization_name}}`, `{{invite_url}}` |
| `issuer-onboarding-invite`     | Standard tenant creation                         | Welcome email for non-government issuers                | `{{organization_name}}`, `{{invite_url}}` |
| `team-invite`                  | Team member invitation                           | Invite for new team members within a tenant             | `{{team_name}}`, `{{invite_url}}`         |
| `verification-onboarding`      | Verifier registration                            | Onboarding guide for verification partners              | `{{organization_name}}`, `{{invite_url}}` |

### Account & security

| Template name              | Trigger                   | Description                                        | Placeholders                              |
| :------------------------- | :------------------------ | :------------------------------------------------- | :---------------------------------------- |
| `email-verification`       | Account signup            | Confirms the user's email address                  | `{{VERIFY_URL}}`                          |
| `password-reset`           | Password reset request    | Secure password reset link                         | `{{reset_url}}`                           |
| `api-key-issued`           | API key creation          | Confirms a new API key was created for the account | `{{key_prefix}}`, `{{created_at}}`        |
| `security-review-response` | Security review completed | Notifies the tenant of a security review outcome   | `{{review_status}}`, `{{review_summary}}` |
| `incident-notification`    | Security incident         | Alerts administrators to a platform security event | `{{incident_title}}`, `{{severity}}`      |

### Issuer lifecycle

| Template name             | Trigger                     | Description                                               | Placeholders                                                                  |
| :------------------------ | :-------------------------- | :-------------------------------------------------------- | :---------------------------------------------------------------------------- |
| `issuer-approved`         | Issuer application approved | Confirms approval and shows the assigned trust tier       | `{{assigned_tier}}`, `{{console_url}}`                                        |
| `issuer-rejected`         | Issuer application rejected | Notifies the applicant with the rejection reason          | `{{rejection_reason}}`                                                        |
| `issuer-evidence-request` | Additional evidence needed  | Requests compliance evidence for a trust tier application | `{{trust_tier_name}}`, `{{requested_evidence_description}}`, `{{upload_url}}` |

### Enterprise onboarding sequence

Government and institutional tenants receive a sequence of lifecycle emails as they progress through onboarding. These are sent automatically at each stage.

| Template name           | Stage               | Description                                                         |
| :---------------------- | :------------------ | :------------------------------------------------------------------ |
| `invite_kickoff`        | Invitation          | Welcome to Truthlocks Enterprise — links to the onboarding wizard   |
| `sandbox_ready`         | Sandbox provisioned | Confirms the sandbox environment is ready for integration testing   |
| `evidence_request`      | Compliance review   | Requests compliance documentation needed for production access      |
| `billing_activation`    | Billing setup       | Confirms billing is active and the subscription plan is in place    |
| `webhooks_test`         | Integration testing | Confirms webhook endpoints are configured and receiving test events |
| `incident_notification` | Ongoing             | Alerts the tenant administrator to any platform incidents           |
| `approval_go_live`      | Production approved | Confirms production access is granted and the tenant is live        |

<Tip>
  The onboarding sequence is fully automated. As a platform admin, you trigger
  it by creating a tenant with the `GOVERNMENT` type or by qualifying a lead in
  the leads pipeline.
</Tip>

### Other notifications

| Template name              | Trigger                      | Description                                        |
| :------------------------- | :--------------------------- | :------------------------------------------------- |
| `billing-upgrade-required` | Usage exceeds plan limits    | Prompts the tenant to upgrade their billing plan   |
| `webhook-test-success`     | Webhook test event delivered | Confirms a test webhook was delivered successfully |

<Info>
  Government tenants (`GOVERNMENT` type) automatically receive the
  `enterprise-onboarding-invite` template instead of the standard issuer
  welcome. See the [B2G Procurement Pack](/packs/enterprise/b2g-procurement)
  for details on the enterprise onboarding flow.
</Info>

## Troubleshooting

### Email not received

1. Check logs for `email_sent` or `email_send_failed` events
2. Verify the recipient email is not in `email_suppressions`
3. Check SES console for sending quota and reputation
4. Verify DKIM/SPF records are correctly configured

### Common error codes

| Error code         | Meaning                                          | Resolution                                                |
| :----------------- | :----------------------------------------------- | :-------------------------------------------------------- |
| `EMAIL_SUPPRESSED` | Recipient is on the suppression list             | Remove from `email_suppressions` if the issue is resolved |
| `SES_ERROR`        | AWS SES rejected the send request                | Check SES console for quota or configuration issues       |
| `CONFIG_ERROR`     | Missing or invalid email configuration           | Verify environment variables are set correctly            |
| `INVALID_EMAIL`    | Recipient email address failed format validation | Confirm the email address is valid                        |

## Observability

All email operations are logged with structured fields:

```json theme={null}
{
  "level": "info",
  "msg": "email_sent",
  "message_id": "0102...",
  "to": "admin@agency.gov",
  "subject": "You've been invited to Truthlocks Enterprise",
  "template_name": "enterprise-onboarding-invite",
  "tenant_id": "tnt_abc123",
  "duration_ms": 245
}
```

Key log events to monitor:

| Event                  | Level | Description                                      |
| :--------------------- | :---- | :----------------------------------------------- |
| `email_sent`           | info  | Email delivered to SES or SMTP successfully      |
| `email_send_failed`    | error | Email send attempt failed                        |
| `email_suppressed`     | warn  | Send skipped because the recipient is suppressed |
| `ses_webhook_received` | info  | SNS notification received from SES               |
| `ses_webhook_error`    | error | Failed to process an SNS notification            |
