┌──────────────────────────────────┬──────────────────────────────────┐
│ 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 to manage │
└──────────────────────────────────┴──────────────────────────────────┘
Use this when you have a Go application and want to send messages directly — no server setup required.
go get github.com/weprodev/wpd-message-gatewaypackage 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)
}// No external service — messages are captured in RAM
gw, _ := gateway.New(gateway.Config{
DefaultEmailProvider: "memory",
})
gw.SendEmail(ctx, email) // captured locally, not sentPass 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"),
},
},
})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)
git clone https://github.com/weprodev/wpd-message-gateway.git
cd wpd-message-gateway
cp configs/local.example.yml configs/local.yml
make startOpen http://localhost:10104 — the Portal UI.
- Go to http://localhost:10104
- Register (email + password), then sign in
- Create a workspace (e.g. "myapp")
- Go to Integrations → add your email provider (Mailgun, etc.)
- Go to API Keys → create an API key (copy the secret — shown once)
- Go to Settings → set dispatch mode (
provider_onlyfor real sends)
curl -X POST http://localhost:10101/v1/email \
-u "wk_abc123:your-secret" \
-H "X-Workspace-Key: myapp" \
-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: myapp" \
-H "Content-Type: application/json" \
-d '{"to": ["user@example.com"], "subject": "Hello", "html": "<h1>World</h1>"}'import requests
response = requests.post(
"http://localhost:10101/v1/email",
auth=("wk_abc123", "your-secret"),
headers={"X-Workspace-Key": "myapp"},
json={
"to": ["user@example.com"],
"subject": "Hello from Python",
"html": "<h1>Hello!</h1>"
}
)
print(response.json())const response = await fetch("http://localhost:10101/v1/email", {
method: "POST",
headers: {
"Authorization": "Basic " + btoa("wk_abc123:your-secret"),
"X-Workspace-Key": "myapp",
"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 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/workspacesUse your workspace API key credentials:
# Basic auth
curl -u "wk_abc123:secret" -H "X-Workspace-Key: myapp" ...
# Bearer (colon-separated)
curl -H "Authorization: Bearer wk_abc123:secret" -H "X-Workspace-Key: myapp" ...// 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"
}'result, err := gw.SendSMS(ctx, &contracts.SMS{
To: []string{"+1234567890"},
Message: "Your verification code is 123456",
})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"},
})result, err := gw.SendChat(ctx, &contracts.ChatMessage{
To: []string{"#channel"},
Message: "Hello from the gateway!",
})| 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 |
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/register |
Create portal user (returns JWT) |
| POST | /api/v1/auth/login |
Login portal user (returns JWT) |
| GET | /api/v1/auth/me |
Current user |
| GET | /api/v1/workspaces |
List my workspaces |
| POST | /api/v1/workspaces |
Create workspace |
| POST | /api/v1/workspaces/join |
Join workspace with PIN |
| GET | /api/v1/workspaces/:wid |
Get workspace |
| PATCH | /api/v1/workspaces/:wid |
Update workspace |
| GET | /api/v1/workspaces/:wid/members |
List members |
| DELETE | /api/v1/workspaces/:wid/members/:uid |
Remove member |
| GET | /api/v1/workspaces/:wid/api-keys |
List API keys |
| POST | /api/v1/workspaces/:wid/api-keys |
Create API key |
| DELETE | /api/v1/workspaces/:wid/api-keys/:kid |
Delete API key |
| POST | /api/v1/workspaces/:wid/api-keys/:kid/regenerate |
Regenerate secret |
| GET | /api/v1/workspaces/:wid/integrations |
List integrations |
| POST | /api/v1/workspaces/:wid/integrations |
Create/update integration |
| DELETE | /api/v1/workspaces/:wid/integrations/:iid |
Delete integration |
| GET | /api/v1/workspaces/:wid/templates |
List templates |
| POST | /api/v1/workspaces/:wid/templates |
Create template |
| PATCH | /api/v1/workspaces/:wid/templates/:tid |
Update template |
| DELETE | /api/v1/workspaces/:wid/templates/:tid |
Delete template |
| GET | /api/v1/workspaces/:wid/settings |
Get settings |
| PATCH | /api/v1/workspaces/:wid/settings |
Update settings |
| GET | /api/v1/workspaces/:wid/logs |
Message logs |
See Portal inbox for full reference. Base: /api/v1/workspaces/:wid/inbox/
environment: local
server:
port: 10101
portal:
jwt_secret: "your-long-secret-here-minimum-32-chars"
jwt_ttl_hours: 72
ui_port: 10104Note: Provider credentials (Mailgun API keys, etc.) are NOT in YAML files. They are configured in the Portal UI and stored encrypted in PostgreSQL.
# 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
# Internal ingest secret (optional)
MESSAGE_INTERNAL_INGEST_SECRET=your-ingest-secretControl how messages are handled per workspace (set in Portal → Settings):
| Mode | Behavior | Use Case |
|---|---|---|
memory_only |
In-process RAM only, not sent | Development, testing |
provider_only |
Sent to real provider, no local copy | Production |
memory_and_provider |
Both — local copy + real send | Staging, debugging |
Using a Mailgun Sandbox Domain:
- Log into Mailgun → Sending → Domains → [Sandbox]
- Add recipient email to Authorized Recipients
- Recipient must click the verification link
Check workspace dispatch mode in Portal → Settings. If it's memory_only, messages are captured locally — not sent to the provider.
- Ensure
X-Workspace-Keymatches the workspace'sunique_key(slug, not UUID) - Verify the API key is active (Portal → API Keys)
- Check
client_idandclient_secret— secret is shown once at creation
- Architecture — System design
- Portal inbox — Memory capture and inbox UI
- E2E Testing — Automated testing patterns
- Contributing — Adding providers