Skip to content

Latest commit

 

History

History
476 lines (354 loc) · 16.4 KB

File metadata and controls

476 lines (354 loc) · 16.4 KB

Usage Guide

Two Ways to Use Message Gateway

┌──────────────────────────────────┬──────────────────────────────────┐
│  Embedded Go SDK                 │  HTTP Server (any language)      │
│  (no server needed)              │  (full infrastructure)           │
├──────────────────────────────────┼──────────────────────────────────┤
│  go get .../wpd-message-gateway  │  git clone → make start          │
│  gateway.New(config)             │  POST /v1/email (HTTP)           │
│  Config lives in your code       │  Config lives in PostgreSQL      │
│  No DB required                  │  React Portal UI                 │
└──────────────────────────────────┴──────────────────────────────────┘

Option 1: Go Package (Embedded SDK)

Use this when you have a Go application and want to send messages directly — no server setup required.

Install

go get github.com/weprodev/wpd-message-gateway

Basic Usage

package main

import (
    "context"
    "log"

    "github.com/weprodev/wpd-message-gateway/pkg/contracts"
    "github.com/weprodev/wpd-message-gateway/pkg/gateway"
)

func main() {
    gw, err := gateway.New(gateway.Config{
        DefaultEmailProvider: "mailgun",
        EmailProviders: map[string]gateway.EmailConfig{
            "mailgun": {
                CommonConfig: gateway.CommonConfig{APIKey: "key-xxx"},
                Domain:       "mg.example.com",
                FromEmail:    "noreply@example.com",
                FromName:     "MyApp",
            },
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    result, err := gw.SendEmail(context.Background(), &contracts.Email{
        To:      []string{"user@example.com"},
        Subject: "Welcome!",
        HTML:    "<h1>Hello!</h1>",
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("Sent! ID: %s", result.ID)
}

Using Memory Provider (Dev / Tests)

// No external service — messages are captured in RAM
gw, _ := gateway.New(gateway.Config{
    DefaultEmailProvider: "memory",
})
gw.SendEmail(ctx, email) // captured locally, not sent

Provider Configuration

Pass credentials directly to gateway.New(). Use environment variables or your secrets manager — never hard-code credentials.

gw, _ := gateway.New(gateway.Config{
    DefaultEmailProvider: os.Getenv("EMAIL_PROVIDER"), // "mailgun", "memory", etc.
    EmailProviders: map[string]gateway.EmailConfig{
        "mailgun": {
            CommonConfig: gateway.CommonConfig{
                APIKey: os.Getenv("MAILGUN_API_KEY"),
            },
            Domain:    os.Getenv("MAILGUN_DOMAIN"),
            FromEmail: os.Getenv("MAILGUN_FROM_EMAIL"),
            FromName:  os.Getenv("MAILGUN_FROM_NAME"),
        },
    },
})

Option 2: HTTP Server

Use this when you want to:

  • Send messages from any language (Python, PHP, Ruby, JS, etc.)
  • Manage provider credentials via a UI (not in code)
  • Have a team managing multiple workspaces
  • Use the built-in message inbox (dev/testing)

Setup

Option A: Docker Compose (Recommended)

This spins up the entire stack including a PostgreSQL database (pre-loaded with migrations and permission/demo seeds), a hot-reloading Go backend (via air), and the Portal UI dev server:

# Start all services
make dev

# Stop all services
make dev-down

# Reset Database: Since DB init scripts run only on first boot, run this to reset data/schema:
docker compose down -v

Open http://localhost:10104 — the Portal UI.

Option B: Native Setup (No Docker)

If you prefer to run components directly on your host machine:

git clone https://github.com/weprodev/wpd-message-gateway.git
cd wpd-message-gateway
cp configs/local.example.yml configs/local.yml
make start

For Option B, you must manually run PostgreSQL locally, apply schema migrations, and apply the SQL seeds (see below).

Open http://localhost:10104 — the Portal UI.

First time setup (Portal UI)

The Portal UI supports:

  1. Register and sign in (email + password)
  2. Workspaces — list, create, and join workspaces
  3. Integrations — connect, activate, deactivate, or remove messaging providers
  4. Message logs — audit trail of gateway send requests (Overview + channel tabs)
  5. Email inbox — browse captured outbound emails (memory dispatch or store_message_content)
  6. Email templates — create and manage reusable email layouts
  7. Settings — general workspace settings, API keys, and message dispatch mode

Use the Get started dialog on the logs pages for curl examples. Send traffic through the public gateway API (POST /v1/email, POST /v1/sms, etc.) with a workspace API key.

For local dev with PostgreSQL, run migrations then apply seeds in order (see database/init-db.sh): 001_seed_permissions.sql, 002_seed_providers.sql, 003_seed_mailgun_config.sql, and 004_demo_workspace.sql (optional demo data: demo@weprodev.com, member@weprodev.com, viewer@weprodev.com — password secret for all).

Sending via HTTP

X-Workspace-Key accepts the workspace UUID (recommended — copy from Settings or the Get started dialog) or the workspace slug.

curl -X POST http://localhost:10101/v1/email \
  -u "wk_abc123:your-secret" \
  -H "X-Workspace-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["user@example.com"],
    "subject": "Hello",
    "html": "<h1>World</h1>"
  }'

Or with Bearer token:

curl -X POST http://localhost:10101/v1/email \
  -H "Authorization: Bearer wk_abc123:your-secret" \
  -H "X-Workspace-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{"to": ["user@example.com"], "subject": "Hello", "html": "<h1>World</h1>"}'

Sending from Python

import requests

response = requests.post(
    "http://localhost:10101/v1/email",
    auth=("wk_abc123", "your-secret"),
    headers={"X-Workspace-Key": "550e8400-e29b-41d4-a716-446655440000"},
    json={
        "to": ["user@example.com"],
        "subject": "Hello from Python",
        "html": "<h1>Hello!</h1>"
    }
)
print(response.json())

Sending from Node.js / TypeScript

const response = await fetch("http://localhost:10101/v1/email", {
  method: "POST",
  headers: {
    "Authorization": "Basic " + btoa("wk_abc123:your-secret"),
    "X-Workspace-Key": "550e8400-e29b-41d4-a716-446655440000",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: ["user@example.com"],
    subject: "Hello from JS",
    html: "<h1>Hello!</h1>",
  }),
});
const result = await response.json();

Authentication

Portal (UI & Management API /api/v1/*)

Authentication is email + password — the Portal returns a JWT used for all /api/v1/* requests.

# Login (or register first, then login)
curl -X POST http://localhost:10101/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "you@example.com", "password": "your-password"}'

# Response:
# { "token": "<jwt>", "user": { "id": "...", "email": "..." } }

Use the JWT for all /api/v1/* requests:

curl -H "Authorization: Bearer <jwt>" http://localhost:10101/api/v1/workspaces

Send API (/v1/*)

Use your workspace API key credentials:

# Basic auth
curl -u "wk_abc123:secret" -H "X-Workspace-Key: <workspace-uuid>" ...

# Bearer (colon-separated)
curl -H "Authorization: Bearer wk_abc123:secret" -H "X-Workspace-Key: <workspace-uuid>" ...

Message Types

Email

// Go SDK
result, err := gw.SendEmail(ctx, &contracts.Email{
    To:          []string{"user@example.com"},
    CC:          []string{"cc@example.com"},         // optional
    BCC:         []string{"bcc@example.com"},        // optional
    Subject:     "Hello",
    HTML:        "<h1>HTML body</h1>",
    PlainText:   "Plain text fallback",              // optional
    ReplyTo:     "reply@example.com",                // optional
})
# HTTP API
curl -X POST /v1/email -d '{
  "to": ["user@example.com"],
  "cc": ["cc@example.com"],
  "subject": "Hello",
  "html": "<h1>HTML body</h1>",
  "plain_text": "Plain text fallback"
}'

SMS

result, err := gw.SendSMS(ctx, &contracts.SMS{
    To:      []string{"+1234567890"},
    Message: "Your verification code is 123456",
})

Push Notification

result, err := gw.SendPush(ctx, &contracts.PushNotification{
    DeviceTokens: []string{"device-token-1"},
    Title:        "New Message",
    Body:         "You have a new message",
    Data:         map[string]string{"action": "open_chat"},
})

Chat (Slack, WhatsApp, Telegram)

result, err := gw.SendChat(ctx, &contracts.ChatMessage{
    To:      []string{"#channel"},
    Message: "Hello from the gateway!",
})

HTTP API Reference

Send Endpoints (API Key auth required)

Method Endpoint Auth
POST /v1/email API key + X-Workspace-Key
POST /v1/sms API key + X-Workspace-Key
POST /v1/push API key + X-Workspace-Key
POST /v1/chat API key + X-Workspace-Key

Portal REST API (JWT auth)

Routes registered in internal/presentation/router.go. Portal UI pages exist for auth, workspace list, message logs, email inbox, integrations, and settings — other management routes are REST-only until UI is built.

Auth

Method Endpoint Portal UI Description
POST /api/v1/auth/register Create portal user (first_name, last_name, email, password)
POST /api/v1/auth/login Login (returns JWT)
GET /api/v1/auth/verify-email Email verification link handler
GET /api/v1/auth/me Current user (JWT)

Workspaces & messaging (Portal UI)

Method Endpoint Portal UI Description
GET /api/v1/workspaces List workspaces for the logged-in user
GET /api/v1/workspaces/:wid/members List workspace members and roles (members.read)
DELETE /api/v1/workspaces/:wid/members/:userId Remove a member (members.write)
GET /api/v1/workspaces/:wid/invitations List pending invitations (invitations.read)
POST /api/v1/workspaces/:wid/invitations Invite a user with role (invitations.write; body: email, role)
GET /api/v1/workspaces/:wid/logs Message request logs (optionally filter by channel)

Additional workspace endpoints (create, API keys, settings, templates, inbox) are implemented in internal/presentation/router.go and covered in Portal inbox and E2E testing.

Workspace access (portal JWT routes & RBAC)

Workspace-scoped routes require a valid portal JWT and are governed by a Role-Based Access Control (RBAC) middleware backed by wpd-gogate.

Roles and permissions are defined in permission.go and seeded by database/seeds/001_seed_permissions.sql:

  • admin: Full read/write access. The workspace creator is assigned this role automatically (in both workspace_members and wpd-gogate model_has_roles).
  • member: Manage integrations, templates, settings, API keys, and inbox content. Cannot change workspace metadata, manage members, or manage invitations.
  • viewer: Read-only (*.read permissions). Used for invited/read-only members.

Public workspace guests (non-members on visibility = public workspaces) receive only workspaces.read and templates.read (PublicGuestPermissions). The portal UI uses the role and permissions fields returned by GET /api/v1/workspaces for conditional rendering via frontend/src/core/auth/.

To bootstrap roles and permissions, run the database seeds:

  1. Run database/seeds/001_seed_permissions.sql to populate roles (admin, member, viewer), permissions, and role-to-permission mappings.
  2. Run database/seeds/002_seed_providers.sql to seed the provider catalog (memory, mailgun, etc.).
  3. Run database/seeds/003_seed_mailgun_config.sql to seed Mailgun Portal config field metadata.
  4. Run database/seeds/004_demo_workspace.sql (optional) — demo workspace with three portal users (demo@weprodev.com, member@weprodev.com, viewer@weprodev.com; password secret), admin/member/viewer roles, memory integration, API key (demo-client-id / demo-secret).

Dual Authorization Models

The HTTP server uses two distinct authorization approaches by design:

  • Portal Management API (/api/v1/workspaces/:wid/...): Restricts access using JWT tokens combined with fine-grained wpd-gogate permission middleware (RequirePermission).
  • Inbox SSE API (/api/v1/workspaces/:wid/inbox/...): Restricts access using JWT tokens combined with a direct database workspace membership check (RequireWorkspaceMember) and the workspace API key (RequireWorkspaceAPIKey). This decoupled model allows client-side SDKs, SSE event streaming, and automation runners to interact with the simulated inbox without requiring full portal RBAC configuration.

Inbox API

Captured messages are available through the Portal Email page (UI + SSE) and the REST inbox endpoints documented in Portal inbox. SMS, push, and chat channels expose request logs in the UI; captured-message browsing for those channels is API-only today.


Configuration

Server (configs/local.yml)

environment: local

server:
  port: 10101

portal:
  jwt_secret: "your-long-secret-here-minimum-32-chars"
  jwt_ttl_hours: 72
  ui_port: 10104

Note: Provider credentials (Mailgun API keys, etc.) are not in YAML files. Configure them on the Portal Integrations page or via POST /api/v1/workspaces/:wid/integrations. Values are stored encrypted in PostgreSQL.

Environment Variables (Server)

# PostgreSQL connection
DATABASE_URL=postgres://user:pass@localhost:5432/gateway?sslmode=disable
# or individual components:
DB_HOST=localhost
DB_PORT=5432
DB_USER=gateway
DB_PASSWORD=secret
DB_NAME=gateway

# Portal JWT (override yaml)
MESSAGE_JWT_SECRET=your-jwt-secret

# AES encryption key for provider credentials (32 bytes)
MESSAGE_CONFIG_ENCRYPTION_KEY=your-32-byte-key-here

Message Dispatch & Content Storage

Control how messages are routed and whether bodies are captured in the portal inbox.

Defaults: message_dispatch_mode=memory, store_message_content=false.

Invalid values for dispatch settings return 400 Bad Request on PATCH.

Set via Portal Settings → Message Dispatch or REST:

PATCH /api/v1/workspaces/:wid/settings
Authorization: Bearer <portal-jwt>
Content-Type: application/json

{ "message_dispatch_mode": "provider", "store_message_content": "true" }

Portal UI: select Memory or Provider, then check Store message content in inbox on or off.

See Portal inbox for routing behavior.

Every send also creates a message_request_logs row for operational tracing (correlation via request_id).


Troubleshooting

Mailgun: 403 Forbidden

Using a Mailgun Sandbox Domain:

  1. Log into Mailgun → Sending → Domains → [Sandbox]
  2. Add recipient email to Authorized Recipients
  3. Recipient must click the verification link

Provider Not Sending

Check workspace settings via GET /api/v1/workspaces/:wid/settings. If message_dispatch_mode is memory, messages are captured locally — not sent to the provider.

API Key Rejected

  • Ensure X-Workspace-Key matches the workspace UUID or slug
  • Verify the API key is active (GET /api/v1/workspaces/:wid/api-keys with portal JWT)
  • Check client_id and client_secret — secret is shown once at creation

Related Documentation