Sanitized mirror from private repository - 2026-04-20 01:32:01 UTC
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped

This commit is contained in:
Gitea Mirror Bot
2026-04-20 01:32:01 +00:00
commit e7652c8dab
1445 changed files with 364095 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
# Mastodon Federation Guide
## What is Federation?
Federation allows your Mastodon instance to communicate with other Mastodon instances (and other ActivityPub-compatible servers). Users can follow accounts on other servers, and posts are shared across the network.
## Federation Requirements
### 1. HTTPS (Required)
Federation only works over HTTPS. Cloudflare provides this automatically when proxying is enabled.
### 2. Correct Domain Configuration
```env
# .env.production
LOCAL_DOMAIN=mastodon.vish.gg
```
⚠️ **Warning**: Changing LOCAL_DOMAIN after setup will break existing accounts!
### 3. Webfinger Endpoint
Must respond correctly at:
```
https://mastodon.vish.gg/.well-known/webfinger?resource=acct:username@mastodon.vish.gg
```
Expected response:
```json
{
"subject": "acct:vish@mastodon.vish.gg",
"aliases": [
"https://mastodon.vish.gg/@vish",
"https://mastodon.vish.gg/users/vish"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://mastodon.vish.gg/@vish"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://mastodon.vish.gg/users/vish"
}
]
}
```
### 4. ActivityPub Actor Endpoint
Must respond at:
```
https://mastodon.vish.gg/users/vish
```
With `Accept: application/activity+json` header.
## Testing Federation
### Test Webfinger (from external server)
```bash
curl "https://mastodon.vish.gg/.well-known/webfinger?resource=acct:vish@mastodon.vish.gg"
```
### Test Actor Endpoint
```bash
curl -H "Accept: application/activity+json" "https://mastodon.vish.gg/users/vish"
```
### Test Outbound Federation
Search for a remote user in your Mastodon instance:
1. Go to https://mastodon.vish.gg
2. Search for `@Gargron@mastodon.social`
3. If federation works, you'll see the user's profile
### Test from Another Instance
Go to any public Mastodon instance and search for:
```
@vish@mastodon.vish.gg
```
## Cloudflare Configuration
### Required Settings
1. **Proxy Status**: Orange cloud (Proxied) ✅
2. **SSL/TLS Mode**: Full (strict)
3. **Cache Level**: Standard (or Bypass for API endpoints)
### Origin Rules (if using non-standard ports)
Since nginx listens on port 8082, configure an origin rule:
**Rule**:
- If hostname equals `mastodon.vish.gg`
- Then: Override destination port to 8082
### Firewall Rules
Ensure port 8082 is accessible from Cloudflare IPs or use Cloudflare Tunnel.
## Common Federation Issues
### Issue: Remote users can't find your instance
**Cause**: DNS not properly configured or Cloudflare not proxying
**Fix**:
1. Verify DNS A record points to your server
2. Enable Cloudflare proxy (orange cloud)
3. Wait for DNS propagation
### Issue: Webfinger returns 301 redirect
**Normal behavior**: Mastodon redirects HTTP to HTTPS
**Solution**: Ensure requests come via HTTPS
### Issue: Cannot follow remote users
**Cause**: Outbound connections blocked
**Fix**:
1. Check firewall allows outbound HTTPS (443)
2. Verify sidekiq is running: `docker compose ps`
3. Check sidekiq logs: `docker compose logs sidekiq`
### Issue: Federation lag
**Cause**: High queue backlog in sidekiq
**Fix**:
```bash
# Check queue status
docker compose exec web bin/tootctl sidekiq status
# Clear dead jobs if needed
docker compose exec web bin/tootctl sidekiq kill
```
## Federation Debug Commands
```bash
# Check instance connectivity
cd /opt/mastodon
docker compose exec web bin/tootctl domains crawl mastodon.social
# Refresh a remote account
docker compose exec web bin/tootctl accounts refresh @Gargron@mastodon.social
# Clear delivery failures
docker compose exec web bin/tootctl domains purge <domain>
```
## Security Considerations
### Block/Allow Lists
Configure in Admin → Federation:
- Block specific domains
- Silence (limit) specific domains
- Allow specific domains (whitelist mode)
### Rate Limiting
Mastodon has built-in rate limiting for federation requests to prevent abuse.
## Monitoring Federation Health
### Check Sidekiq Queues
```bash
docker compose exec web bin/tootctl sidekiq stats
```
Healthy queues should have:
- Low `push` queue (outbound deliveries)
- Low `pull` queue (fetching remote content)
- Minimal retries
### Check Federation Stats
In Admin → Dashboard:
- Known instances count
- Active users (remote)
- Incoming/outgoing messages

View File

@@ -0,0 +1,321 @@
# Matrix Synapse Setup
This VM runs **two Matrix Synapse instances**:
| Instance | server_name | Domain | Federation | Purpose |
|----------|-------------|--------|------------|---------|
| **Primary** | `mx.vish.gg` | https://mx.vish.gg | ✅ Yes | Main server with federation |
| **Legacy** | `vish` | https://matrix.thevish.io | ❌ No | Historical data archive |
## Architecture
```
Internet
┌────────┴────────┐
│ Cloudflare │
└────────┬────────┘
┌─────────────┴─────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ mx.vish.gg │ │ matrix.thevish.io│
│ (port 443) │ │ (port 443) │
└────────┬────────┘ └────────┬─────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Synology Reverse│ │ Synology Reverse│
│ Proxy → :8082 │ │ Proxy → :8081 │
└────────┬────────┘ └────────┬─────────┘
│ │
└───────────┬───────────────┘
┌─────────────────────────────────────┐
│ Ubuntu VM (192.168.0.154) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Nginx :8082 │ │ Nginx :8081 │ │
│ │ mx.vish.gg │ │ thevish.io │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Synapse:8018 │ │ Synapse:8008 │ │
│ │ mx.vish.gg │ │ vish │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ synapse_mx │ │ synapse │ │
│ │ PostgreSQL │ │ PostgreSQL │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────┘
```
## Primary Server: mx.vish.gg
**This is the main server with federation enabled.**
### Configuration
- **Location**: `/opt/synapse-mx/`
- **Config**: `/opt/synapse-mx/homeserver.yaml`
- **Signing Key**: `/opt/synapse-mx/mx.vish.gg.signing.key`
- **Media Store**: `/opt/synapse-mx/media_store/`
- **Database**: `synapse_mx` (user: `synapse_mx`)
- **Port**: 8018 (Synapse) → 8082 (Nginx)
### User IDs
Users on this server have IDs like: `@username:mx.vish.gg`
### Federation
- ✅ Can communicate with matrix.org and other federated servers
- ✅ Can join public rooms on other servers
- ✅ Other users can find and message your users
### Managing the Service
```bash
sudo systemctl start synapse-mx
sudo systemctl stop synapse-mx
sudo systemctl restart synapse-mx
sudo systemctl status synapse-mx
```
Service file: `/etc/systemd/system/synapse-mx.service`
## Legacy Server: vish (matrix.thevish.io)
**This server contains historical data and cannot federate.**
### Why No Federation?
The `server_name` is `vish` which is not a valid domain. Other Matrix servers cannot discover it because:
- No DNS record for `vish`
- Cannot serve `.well-known` at `https://vish/`
### Configuration
- **Location**: `/opt/synapse/`
- **Config**: `/opt/synapse/homeserver.yaml`
- **Signing Key**: `/opt/synapse/vish.signing.key`
- **Media Store**: `/opt/synapse/media_store/`
- **Database**: `synapse` (user: `synapse`)
- **Port**: 8008 (Synapse) → 8081 (Nginx)
### User IDs
Users on this server have IDs like: `@username:vish`
### Managing the Service
```bash
sudo systemctl start synapse
sudo systemctl stop synapse
sudo systemctl restart synapse
sudo systemctl status synapse
```
Service file: `/etc/systemd/system/synapse.service`
## TURN Server (coturn)
TURN server enables voice/video calls to work through NAT.
### Configuration
- **Config**: `/etc/turnserver.conf`
- **Ports**: 3479 (TURN), 5350 (TURNS), 49201-49250 (Media relay UDP)
- **Realm**: `matrix.thevish.io`
- **Auth Secret**: Shared with Synapse (`turn_shared_secret`)
### Key Settings
```ini
listening-port=3479
tls-listening-port=5350
listening-ip=0.0.0.0
external-ip=YOUR_WAN_IP/192.168.0.154
static-auth-secret=<shared-secret>
realm=matrix.thevish.io
min-port=49201
max-port=49250
```
### Port Forwarding Required
| Port | Protocol | Purpose |
|------|----------|---------|
| 3479 | TCP/UDP | TURN |
| 5350 | TCP/UDP | TURNS (TLS) |
| 49201-49250 | UDP | Media relay |
## Element Web
Element Web is served by Nginx for both instances.
### mx.vish.gg
- **Location**: `/opt/element/web/`
- **Config**: `/opt/element/web/config.json`
- **URL**: https://mx.vish.gg/
### matrix.thevish.io
- **Location**: `/opt/element/web-thevish/`
- **Config**: `/opt/element/web-thevish/config.json`
- **URL**: https://matrix.thevish.io/
## Nginx Configuration
### mx.vish.gg (port 8082)
Location: `/etc/nginx/sites-available/mx-vish-gg`
```nginx
server {
listen 8082;
server_name mx.vish.gg;
root /opt/element/web;
location /health { proxy_pass http://127.0.0.1:8018; }
location ~ ^(/_matrix|/_synapse/client) { proxy_pass http://127.0.0.1:8018; }
location /_matrix/federation { proxy_pass http://127.0.0.1:8018; }
location /.well-known/matrix/server { return 200 '{"m.server": "mx.vish.gg:443"}'; }
location /.well-known/matrix/client { return 200 '{"m.homeserver": {"base_url": "https://mx.vish.gg"}}'; }
location / { try_files $uri $uri/ /index.html; }
}
```
### matrix.thevish.io (port 8081)
Location: `/etc/nginx/sites-available/matrix-thevish`
```nginx
server {
listen 8081;
server_name matrix.thevish.io;
root /opt/element/web-thevish;
location /health { proxy_pass http://127.0.0.1:8008; }
location ~ ^(/_matrix|/_synapse/client) { proxy_pass http://127.0.0.1:8008; }
location /.well-known/matrix/server { return 200 '{"m.server": "matrix.thevish.io:443"}'; }
location /.well-known/matrix/client { return 200 '{"m.homeserver": {"base_url": "https://matrix.thevish.io"}}'; }
location / { try_files $uri $uri/ /index.html; }
}
```
## Synology Reverse Proxy
| Name | Source (HTTPS) | Destination (HTTP) |
|------|----------------|-------------------|
| mx_vish_gg | mx.vish.gg:443 | 192.168.0.154:8082 |
| matrix_thevish | matrix.thevish.io:443 | 192.168.0.154:8081 |
## Cloudflare DNS
| Type | Name | Content | Proxy |
|------|------|---------|-------|
| A | mx.vish.gg | YOUR_WAN_IP | ✅ Proxied |
| A | matrix.thevish.io | YOUR_WAN_IP | ✅ Proxied |
## Database Backup
### Backup mx.vish.gg
```bash
sudo -u postgres pg_dump -Fc synapse_mx > synapse_mx_backup_$(date +%Y%m%d).dump
```
### Backup legacy vish
```bash
sudo -u postgres pg_dump -Fc synapse > synapse_vish_backup_$(date +%Y%m%d).dump
```
### Restore
```bash
sudo -u postgres pg_restore -d <database_name> <backup_file.dump>
```
## Testing Federation
Use the Matrix Federation Tester:
```bash
curl -s "https://federationtester.matrix.org/api/report?server_name=mx.vish.gg" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(f'Federation OK: {d.get(\"FederationOK\", False)}')
"
```
## Creating Users
### Via registration (if enabled)
Go to https://mx.vish.gg and click "Create account"
### Via command line
```bash
cd /opt/synapse-mx
sudo -u synapse /opt/synapse/venv/bin/register_new_matrix_user \
-c /opt/synapse-mx/homeserver.yaml \
-u <username> -p <password> -a
```
## Troubleshooting
### Check if Synapse is running
```bash
sudo systemctl status synapse synapse-mx
curl -s http://localhost:8008/_synapse/admin/v1/server_version # legacy
curl -s http://localhost:8018/_synapse/admin/v1/server_version # mx
```
### View logs
```bash
sudo journalctl -u synapse -f # mx.vish.gg
sudo journalctl -u synapse-mx -f # legacy vish
```
### Test health endpoints
```bash
curl http://localhost:8018/health # mx.vish.gg
curl http://localhost:8008/health # legacy vish
```
### Restart nginx
```bash
sudo nginx -t && sudo systemctl reload nginx
```
### DB ownership fix (apply if migrations fail on upgrade)
If Synapse fails to start after upgrade with `InsufficientPrivilege: must be owner of table`,
the DB tables need their ownership corrected. Run for the affected database:
```bash
# For synapse (legacy) DB:
sudo -u postgres psql synapse -t -c "
SELECT 'ALTER TABLE public.' || tablename || ' OWNER TO synapse;'
FROM pg_tables WHERE schemaname='public' AND tableowner <> 'synapse';
" | sudo -u postgres psql synapse
sudo -u postgres psql synapse -t -c "
SELECT 'ALTER SEQUENCE ' || sequence_name || ' OWNER TO synapse;'
FROM information_schema.sequences WHERE sequence_schema='public';
" | sudo -u postgres psql synapse
# For synapse_mx DB, replace 'synapse' with 'synapse_mx' throughout
```

View File

@@ -0,0 +1,259 @@
# Deployment Documentation
Complete setup guide for the Ubuntu VM Homelab with Mastodon, Mattermost, and Matrix/Element.
## Server Access
```
IP: YOUR_WAN_IP
SSH Port: 65533
Username: test
Password: "REDACTED_PASSWORD"
```
## Service Credentials
### Mastodon Admin
- **Username**: vish
- **Email**: your-email@example.com
- **Password**: `c16a0236e5a5da1e0c80bb296a290fc3`
- **URL**: https://mastodon.vish.gg
### Mattermost
- **URL**: https://mm.crista.love
- **Admin**: (configured during first access)
### Matrix/Element
- **URL**: https://mx.vish.gg
- **Homeserver**: mx.vish.gg
## PostgreSQL Configuration
PostgreSQL 16 is configured to allow Docker container connections:
```
# /etc/postgresql/16/main/pg_hba.conf
host all all 172.17.0.0/16 md5
host all all 0.0.0.0/0 md5
# /etc/postgresql/16/main/postgresql.conf
listen_addresses = '*'
```
### Database Credentials
| Database | User | Password |
|----------|------|----------|
| mastodon_production | mastodon | mastodon_pass_2026 |
| mattermost | mmuser | (check /opt/mattermost/config/config.json) |
| synapse | synapse | (check /opt/synapse/homeserver.yaml) |
## Nginx Configuration
### Ports
- **8080**: Matrix/Element (mx.vish.gg)
- **8081**: Mattermost (mm.crista.love)
- **8082**: Mastodon (mastodon.vish.gg)
### Site Configs
```
/etc/nginx/sites-enabled/
├── mastodon -> /etc/nginx/sites-available/mastodon
├── matrix -> /etc/nginx/sites-available/matrix
└── mattermost -> /etc/nginx/sites-available/mattermost
```
## Mastodon Setup Details
### Directory Structure
```
/opt/mastodon/
├── docker-compose.yml
├── .env.production
├── public/
│ └── system/ # Media uploads
└── redis/ # Redis data
```
### Environment Variables
```env
LOCAL_DOMAIN=mastodon.vish.gg
SINGLE_USER_MODE=false
# Database
DB_HOST=172.17.0.1
DB_PORT=5432
DB_NAME=mastodon_production
DB_USER=mastodon
DB_PASS="REDACTED_PASSWORD"
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
# SMTP (Gmail) - CONFIGURED AND WORKING ✅
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_LOGIN=your-email@example.com
SMTP_PASSWORD="REDACTED_PASSWORD"
SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS="Mastodon <notifications@mastodon.vish.gg>"
# Search
ES_ENABLED=false
```
### Common Commands
```bash
# View logs
cd /opt/mastodon && docker compose logs -f
# Restart services
cd /opt/mastodon && docker compose restart
# Run admin commands
cd /opt/mastodon && docker compose exec web bin/tootctl <command>
# Create new user
docker compose run --rm web bin/tootctl accounts create USERNAME --email=EMAIL --confirmed --role=Owner
# Database migration
docker compose run --rm web bundle exec rake db:migrate
```
## Mattermost Setup Details
### Directory Structure
```
/opt/mattermost/
├── config/
│ └── config.json
├── data/
├── logs/
├── plugins/
└── client/plugins/
```
### Docker Command
```bash
docker run -d --name mattermost \
-p 8065:8065 \
-v /opt/mattermost/config:/mattermost/config \
-v /opt/mattermost/data:/mattermost/data \
-v /opt/mattermost/logs:/mattermost/logs \
-v /opt/mattermost/plugins:/mattermost/plugins \
--restart=always \
mattermost/mattermost-team-edition:11.3
```
## Matrix/Synapse Setup Details
### Directory Structure
```
/opt/synapse/
├── homeserver.yaml
├── *.signing.key
└── media_store/
/opt/element/web/
└── (Element Web static files)
```
### Synapse Service
```bash
# Status
systemctl status matrix-synapse
# Restart
systemctl restart matrix-synapse
# Logs
journalctl -u matrix-synapse -f
```
## Cloudflare Configuration
For each service, configure Cloudflare:
1. **DNS Records** (A records pointing to VM public IP)
- mastodon.vish.gg
- mm.crista.love
- mx.vish.gg
2. **Origin Rules** (Route to correct nginx port)
- mastodon.vish.gg → Port 8082
- mm.crista.love → Port 8081
- mx.vish.gg → Port 8080
3. **SSL/TLS**: Full (strict)
## Federation (Mastodon)
Federation requires:
1. ✅ Proper LOCAL_DOMAIN in .env.production
2. ✅ HTTPS via Cloudflare
3. ✅ Webfinger endpoint responding at `/.well-known/webfinger`
4. ⏳ DNS properly configured
Test federation:
```bash
# From another server
curl "https://mastodon.vish.gg/.well-known/webfinger?resource=acct:vish@mastodon.vish.gg"
```
## SMTP Configuration (Gmail)
To send emails via Gmail:
1. Enable 2-Factor Authentication on your Google account
2. Generate an App Password:
- Go to https://myaccount.google.com/apppasswords
- Create a new app password for "Mail"
3. Update `/opt/mastodon/.env.production`:
```
SMTP_PASSWORD="REDACTED_PASSWORD"
```
4. Restart Mastodon:
```bash
cd /opt/mastodon && docker compose restart
```
## Backup Locations
```
/backup/
├── YYYYMMDD_HHMMSS/
│ ├── mattermost.sql
│ ├── synapse.sql
│ ├── mastodon.sql
│ ├── mastodon_media.tar.gz
│ ├── mattermost_data.tar.gz
│ └── synapse_data.tar.gz
```
## Troubleshooting
### Mastodon 403 Forbidden
- Normal when accessing with wrong Host header
- Always access via proper domain or use `-H "Host: mastodon.vish.gg"`
### Federation Not Working
- Check Cloudflare proxy is enabled
- Verify DNS resolves correctly
- Test webfinger endpoint externally
### Database Connection Errors
- Verify PostgreSQL is listening on all interfaces
- Check pg_hba.conf allows Docker network
- Restart PostgreSQL: `systemctl restart postgresql`
### Container Won't Start
```bash
# Check logs
docker logs <container_name>
# Check Docker network
docker network ls
docker network inspect mastodon_internal_network
```

View File

@@ -0,0 +1,178 @@
# SMTP Email Configuration
Guide for configuring email delivery for Mastodon and Mattermost.
## Gmail SMTP Setup
### Prerequisites
1. Google account with 2-Factor Authentication enabled
2. App Password generated for "Mail"
### Generate Gmail App Password
1. Go to [Google Account Security](https://myaccount.google.com/security)
2. Enable 2-Step Verification if not already enabled
3. Go to [App Passwords](https://myaccount.google.com/apppasswords)
4. Select "Mail" and your device
5. Click "Generate"
6. Copy the 16-character password
### Mastodon Configuration
Edit `/opt/mastodon/.env.production`:
```env
# SMTP Configuration (Gmail)
SMTP_SERVER=smtp.gmail.com
SMTP_PORT=587
SMTP_LOGIN=your-email@example.com
SMTP_PASSWORD="REDACTED_PASSWORD"
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=none
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS="Mastodon <notifications@mastodon.vish.gg>"
```
Apply changes:
```bash
cd /opt/mastodon && docker compose restart
```
### Test Email Delivery
```bash
# Send test email
cd /opt/mastodon
docker compose exec web bin/tootctl accounts modify vish --confirm
# Or trigger password reset
# Go to login page and click "Forgot password"
```
## Mattermost Email Configuration
Edit `/opt/mattermost/config/config.json`:
```json
{
"EmailSettings": {
"EnableSignUpWithEmail": true,
"EnableSignInWithEmail": true,
"EnableSignInWithUsername": true,
"SendEmailNotifications": true,
"RequireEmailVerification": false,
"FeedbackName": "Mattermost",
"FeedbackEmail": "notifications@mm.crista.love",
"SMTPUsername": "your-email@example.com",
"SMTPPassword": "your_16_char_app_password",
"SMTPServer": "smtp.gmail.com",
"SMTPPort": "587",
"ConnectionSecurity": "STARTTLS",
"SendPushNotifications": true
}
}
```
Restart Mattermost:
```bash
docker restart mattermost
```
## Alternative: SendGrid
### Setup
1. Create SendGrid account at https://sendgrid.com
2. Generate API key with "Mail Send" permission
### Mastodon Configuration
```env
SMTP_SERVER=smtp.sendgrid.net
SMTP_PORT=587
SMTP_LOGIN=apikey
SMTP_PASSWORD="REDACTED_PASSWORD"
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=peer
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS="Mastodon <notifications@mastodon.vish.gg>"
```
## Alternative: Mailgun
### Setup
1. Create Mailgun account at https://mailgun.com
2. Verify your domain
3. Get SMTP credentials
### Mastodon Configuration
```env
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
SMTP_LOGIN=postmaster@mg.yourdomain.com
SMTP_PASSWORD="REDACTED_PASSWORD"
SMTP_AUTH_METHOD=plain
SMTP_OPENSSL_VERIFY_MODE=peer
SMTP_ENABLE_STARTTLS=auto
SMTP_FROM_ADDRESS="Mastodon <notifications@mastodon.vish.gg>"
```
## Troubleshooting
### Check SMTP Connection
```bash
# Test from container
docker compose exec web bash -c "echo 'test' | openssl s_client -connect smtp.gmail.com:587 -starttls smtp"
```
### Check Sidekiq Mail Queue
```bash
# View failed email jobs
docker compose exec web bin/tootctl sidekiq status
```
### Common Errors
#### "Username and Password not accepted"
- Verify App Password is correct (not your regular password)
- Ensure 2FA is enabled on Google account
- Check no extra spaces in password
#### "Connection refused"
- Firewall blocking outbound port 587
- Try port 465 with SSL instead
#### "Certificate verify failed"
- Set `SMTP_OPENSSL_VERIFY_MODE=none` (less secure)
- Or ensure CA certificates are up to date
### Gmail-Specific Issues
#### "Less secure app access"
- Not needed when using App Passwords
- App Passwords bypass this requirement
#### "Critical security alert"
- Normal for first connection from new IP
- Confirm it was you in Google Security settings
## Email Content Customization
### Mastodon
Email templates are in the Mastodon source code. Custom templates require forking.
### Mattermost
Edit in System Console → Site Configuration → Customization
- Support Email
- Notification Footer
- Custom Branding
## SPF/DKIM/DMARC
For better deliverability, configure DNS records:
### SPF Record
```
TXT @ "v=spf1 include:_spf.google.com ~all"
```
### Note on Gmail Sending
When using Gmail SMTP, emails are sent "via gmail.com" which has good deliverability. Custom domain email requires additional DNS setup.