Compare commits

...

4 Commits

Author SHA1 Message Date
Jeff Emmett 3de0b9e695 Fix redacted placeholders in public-facing files
Restore contact email in website (public-facing).
Use env var reference for workflow notification address.
Clean up backlog task notes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:03:33 +00:00
Jeff Emmett 0f582c71ba Move all SMTP config to env vars, no hardcoded values
SMTP host, port, and sender address are now sourced from
.env like all other credentials. No service-identifying
details remain in tracked files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 01:02:39 +00:00
Jeff Emmett 1d1677fa6f Remove hardcoded DB password default from docker-compose
All secrets now come from .env file on the server.
No default/fallback values for any credentials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 00:50:04 +00:00
Jeff Emmett d354e3d2d9 Switch n8n email from Resend API to Mailcow SMTP
Replace all Resend HTTP API calls with n8n built-in emailSend
nodes using Mailcow SMTP at the SMTP server.
Add SMTP env vars and n8n API key to docker-compose config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 00:30:05 +00:00
6 changed files with 82 additions and 98 deletions

View File

@ -36,7 +36,7 @@ Set up CosmoLocal World infrastructure: Mailcow SMTP for cosmolocal.world, Docmo
### Mailcow (cosmolocal.world)
- Mailbox: noreply@cosmolocal.world
- Alias: newsletter@cosmolocal.world → noreply@cosmolocal.world (sender_allowed=1)
- SMTP: ***REDACTED_SMTP_HOST***:465 (TLS)
- SMTP: [SMTP_HOST]:465 (TLS)
- DNS: SPF, DKIM (2048-bit), DMARC all configured on Cloudflare
- Google Postmaster Tools verified

View File

@ -29,10 +29,17 @@ services:
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD:-***REDACTED_DB_PASS***}
- DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
- TWENTY_API_KEY=${TWENTY_API_KEY}
- RESEND_API_KEY=${RESEND_API_KEY}
- LISTMONK_CREDENTIALS=${LISTMONK_CREDENTIALS}
- N8N_SMTP_HOST=${SMTP_HOST}
- N8N_SMTP_PORT=${SMTP_PORT}
- N8N_SMTP_USER=${SMTP_USER}
- N8N_SMTP_PASS=${SMTP_PASS}
- N8N_SMTP_SENDER=${SMTP_SENDER}
- N8N_EMAIL_MODE=smtp
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- n8n-cosmolocal-data:/home/node/.n8n
labels:
@ -58,7 +65,7 @@ services:
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD:-***REDACTED_DB_PASS***}
- POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
volumes:
- n8n-cosmolocal-db:/var/lib/postgresql/data
healthcheck:

View File

@ -48,31 +48,24 @@
},
{
"parameters": {
"method": "POST",
"url": "https://api.resend.com/emails",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.RESEND_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"from\": \"Cosmolocal Foundation <***REDACTED_EMAIL***>\",\n \"to\": [\"{{ $json.email }}\"],\n \"subject\": \"Welcome to the Cosmolocal Foundation\",\n \"html\": \"<h2>Welcome, {{ $json.firstName }}!</h2><p>Thank you for connecting with the Cosmolocal Foundation. We're building a world where local communities thrive within regenerative economies, connected through global knowledge-sharing and commons-based collaboration.</p><p><strong>\\\"What is heavy should be local, and what is light should be global and shared.\\\"</strong></p><p>Here's what we're working on:</p><ul><li><strong>Decentralized Governance</strong> — Transparent, community-led decision-making</li><li><strong>Open Knowledge Commons</strong> — Sharing sustainable production methods globally</li><li><strong>Commons-Compatible Capital</strong> — Financing transformative local projects</li><li><strong>Cosmolocal Coordination</strong> — Connecting local efforts into a global network</li></ul><p>We'll be in touch with updates on our progress and opportunities to get involved.</p><p>Warm regards,<br/>The Cosmolocal Foundation Team</p><p style='color:#78716c;font-size:12px;'>cosmolocal.world</p>\"\n}",
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
"toEmail": "={{ $json.email }}",
"subject": "Welcome to the Cosmolocal Foundation",
"emailType": "html",
"html": "<h2>Welcome, {{ $json.firstName }}!</h2><p>Thank you for connecting with the Cosmolocal Foundation. We're building a world where local communities thrive within regenerative economies, connected through global knowledge-sharing and commons-based collaboration.</p><p><strong>\"What is heavy should be local, and what is light should be global and shared.\"</strong></p><p>Here's what we're working on:</p><ul><li><strong>Decentralized Governance</strong> — Transparent, community-led decision-making</li><li><strong>Open Knowledge Commons</strong> — Sharing sustainable production methods globally</li><li><strong>Commons-Compatible Capital</strong> — Financing transformative local projects</li><li><strong>Cosmolocal Coordination</strong> — Connecting local efforts into a global network</li></ul><p>We'll be in touch with updates on our progress and opportunities to get involved.</p><p>Warm regards,<br/>The Cosmolocal Foundation Team</p><p style='color:#78716c;font-size:12px;'>cosmolocal.world</p>",
"options": {}
},
"id": "send-welcome-email",
"name": "Send Welcome Email (Day 0)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [680, 300]
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [680, 300],
"credentials": {
"smtp": {
"id": "mailcow-smtp",
"name": "Mailcow SMTP"
}
}
},
{
"parameters": {
@ -87,31 +80,24 @@
},
{
"parameters": {
"method": "POST",
"url": "https://api.resend.com/emails",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.RESEND_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"from\": \"Cosmolocal Foundation <***REDACTED_EMAIL***>\",\n \"to\": [\"{{ $('Extract Contact Fields').item.json.email }}\"],\n \"subject\": \"Our Strategic Priorities — How We're Building Change\",\n \"html\": \"<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We wanted to share more about our strategic approach to systemic transformation.</p><p>The Cosmolocal Foundation is executing on eight key initiatives:</p><ol><li><strong>Mapping Regenerative Communities</strong> — Cataloging eco-villages, circular economy hubs, and governance initiatives worldwide</li><li><strong>Web3 Funding</strong> — Quadratic funding, DAOs, and Collaborative Finance for local projects</li><li><strong>Open Resources</strong> — A global knowledge commons of governance models and production blueprints</li><li><strong>Education & Advocacy</strong> — Engaging policymakers to scale cosmolocal principles</li><li><strong>Pilots & Grants</strong> — Funding projects that demonstrate scalable solutions</li><li><strong>Cosmolocal Certification</strong> — Blockchain-verified standards for cosmolocal initiatives</li><li><strong>Global Alliances</strong> — Bioregional collaboration on governance and mutual aid</li><li><strong>Impact Research</strong> — Evidence-based scaling through the Cosmolocal Research Institute</li></ol><p>Want to learn more or get involved? Reply to this email — we'd love to hear from you.</p><p>Best,<br/>The Cosmolocal Foundation Team</p>\"\n}",
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
"toEmail": "={{ $('Extract Contact Fields').item.json.email }}",
"subject": "Our Strategic Priorities — How We're Building Change",
"emailType": "html",
"html": "<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We wanted to share more about our strategic approach to systemic transformation.</p><p>The Cosmolocal Foundation is executing on eight key initiatives:</p><ol><li><strong>Mapping Regenerative Communities</strong> — Cataloging eco-villages, circular economy hubs, and governance initiatives worldwide</li><li><strong>Web3 Funding</strong> — Quadratic funding, DAOs, and Collaborative Finance for local projects</li><li><strong>Open Resources</strong> — A global knowledge commons of governance models and production blueprints</li><li><strong>Education & Advocacy</strong> — Engaging policymakers to scale cosmolocal principles</li><li><strong>Pilots & Grants</strong> — Funding projects that demonstrate scalable solutions</li><li><strong>Cosmolocal Certification</strong> — Blockchain-verified standards for cosmolocal initiatives</li><li><strong>Global Alliances</strong> — Bioregional collaboration on governance and mutual aid</li><li><strong>Impact Research</strong> — Evidence-based scaling through the Cosmolocal Research Institute</li></ol><p>Want to learn more or get involved? Reply to this email — we'd love to hear from you.</p><p>Best,<br/>The Cosmolocal Foundation Team</p>",
"options": {}
},
"id": "send-followup-email",
"name": "Send Follow-up Email (Day 3)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1120, 300]
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [1120, 300],
"credentials": {
"smtp": {
"id": "mailcow-smtp",
"name": "Mailcow SMTP"
}
}
},
{
"parameters": {
@ -126,31 +112,24 @@
},
{
"parameters": {
"method": "POST",
"url": "https://api.resend.com/emails",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.RESEND_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"from\": \"Cosmolocal Foundation <***REDACTED_EMAIL***>\",\n \"to\": [\"{{ $('Extract Contact Fields').item.json.email }}\"],\n \"subject\": \"Join the Movement — Ways to Participate\",\n \"html\": \"<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We believe in the power of collaboration. Here are ways you can participate in the cosmolocal movement:</p><ul><li><strong>Subscribe to our newsletter</strong> — Stay updated on projects, pilots, and governance experiments</li><li><strong>Join a working group</strong> — Contribute your expertise to research, technology, or community building</li><li><strong>Support a pilot project</strong> — Help fund or participate in cosmolocal demonstrations</li><li><strong>Spread the word</strong> — Share our vision with your network</li></ul><p>Visit <a href='https://cosmolocal.world'>cosmolocal.world</a> to explore our work, or simply reply to this email to start a conversation.</p><p>Together we can build regenerative economies that serve communities and the planet.</p><p>In solidarity,<br/>The Cosmolocal Foundation Team</p>\"\n}",
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
"toEmail": "={{ $('Extract Contact Fields').item.json.email }}",
"subject": "Join the Movement — Ways to Participate",
"emailType": "html",
"html": "<h2>Hi {{ $('Extract Contact Fields').item.json.firstName }},</h2><p>We believe in the power of collaboration. Here are ways you can participate in the cosmolocal movement:</p><ul><li><strong>Subscribe to our newsletter</strong> — Stay updated on projects, pilots, and governance experiments</li><li><strong>Join a working group</strong> — Contribute your expertise to research, technology, or community building</li><li><strong>Support a pilot project</strong> — Help fund or participate in cosmolocal demonstrations</li><li><strong>Spread the word</strong> — Share our vision with your network</li></ul><p>Visit <a href='https://cosmolocal.world'>cosmolocal.world</a> to explore our work, or simply reply to this email to start a conversation.</p><p>Together we can build regenerative economies that serve communities and the planet.</p><p>In solidarity,<br/>The Cosmolocal Foundation Team</p>",
"options": {}
},
"id": "send-engagement-email",
"name": "Send Engagement Email (Day 10)",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1560, 300]
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [1560, 300],
"credentials": {
"smtp": {
"id": "mailcow-smtp",
"name": "Mailcow SMTP"
}
}
},
{
"parameters": {

View File

@ -101,7 +101,7 @@
},
{
"parameters": {
"jsCode": "const items = $input.all();\nconst contactList = items.map(item => \n `- **${item.json.fullName}** (${item.json.email}) — ${item.json.daysSinceUpdate} days since last update`\n).join('\\n');\n\nconst count = items.length;\n\nreturn [{\n json: {\n subject: `[Cosmolocal CRM] ${count} contact${count !== 1 ? 's' : ''} need${count === 1 ? 's' : ''} follow-up`,\n htmlBody: `<h2>Stale Contact Report</h2><p>The following ${count} contact${count !== 1 ? 's have' : ' has'} not been updated in 14+ days:</p><table style=\"border-collapse:collapse;width:100%\"><tr style=\"background:#f5f5f4\"><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Name</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Email</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Days Stale</th></tr>${items.map(i => `<tr><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.fullName}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.email}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.daysSinceUpdate}</td></tr>`).join('')}</table><p style=\"margin-top:16px\"><a href=\"https://crm.cosmolocal.world\">Open CRM</a> to follow up.</p><p style=\"color:#78716c;font-size:12px\">Automated by n8n — automate.cosmolocal.world</p>`,\n count\n }\n}];"
"jsCode": "const items = $input.all();\nconst count = items.length;\n\nconst tableRows = items.map(i => `<tr><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.fullName}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.email}</td><td style=\"padding:8px;border:1px solid #e7e5e4\">${i.json.daysSinceUpdate}</td></tr>`).join('');\n\nreturn [{\n json: {\n subject: `[Cosmolocal CRM] ${count} contact${count !== 1 ? 's' : ''} need${count === 1 ? 's' : ''} follow-up`,\n htmlBody: `<h2>Stale Contact Report</h2><p>The following ${count} contact${count !== 1 ? 's have' : ' has'} not been updated in 14+ days:</p><table style=\"border-collapse:collapse;width:100%\"><tr style=\"background:#f5f5f4\"><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Name</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Email</th><th style=\"padding:8px;text-align:left;border:1px solid #e7e5e4\">Days Stale</th></tr>${tableRows}</table><p style=\"margin-top:16px\"><a href=\"https://crm.cosmolocal.world\">Open CRM</a> to follow up.</p><p style=\"color:#78716c;font-size:12px\">Automated by n8n — automate.cosmolocal.world</p>`,\n count\n }\n}];"
},
"id": "build-report",
"name": "Build Report Email",
@ -111,31 +111,24 @@
},
{
"parameters": {
"method": "POST",
"url": "https://api.resend.com/emails",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $env.RESEND_API_KEY }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"from\": \"CRM Bot <automate@cosmolocal.world>\",\n \"to\": [\"***REDACTED_EMAIL***\"],\n \"subject\": \"{{ $json.subject }}\",\n \"html\": \"{{ $json.htmlBody }}\"\n}",
"fromEmail": "={{ $env.N8N_SMTP_SENDER }}",
"toEmail": "={{ $env.N8N_SMTP_SENDER }}",
"subject": "={{ $json.subject }}",
"emailType": "html",
"html": "={{ $json.htmlBody }}",
"options": {}
},
"id": "send-reminder-email",
"name": "Send Reminder to Team",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [1560, 240]
"type": "n8n-nodes-base.emailSend",
"typeVersion": 2.1,
"position": [1560, 240],
"credentials": {
"smtp": {
"id": "mailcow-smtp",
"name": "Mailcow SMTP"
}
}
},
{
"parameters": {},

View File

@ -6,13 +6,18 @@ Import these JSON files into [automate.cosmolocal.world](https://automate.cosmol
API keys are passed as **Docker environment variables** in `docker-compose.yml` (n8n community edition doesn't support Settings > Variables). The workflows access them via `$env.VARIABLE_NAME`.
| Variable | Description | Where to find |
|----------|-------------|---------------|
| `TWENTY_API_KEY` | Twenty CRM API key | crm.cosmolocal.world > Settings > API Keys |
| `RESEND_API_KEY` | Resend email API key | `ssh netcup "cat ~/.resend_credentials"` |
| `LISTMONK_CREDENTIALS` | Listmonk `user:pass` | Internal Docker service credentials |
| Variable | Description |
|----------|-------------|
| `TWENTY_API_KEY` | CRM API key |
| `SMTP_HOST` | SMTP server hostname |
| `SMTP_PORT` | SMTP server port |
| `SMTP_USER` | SMTP username |
| `SMTP_PASS` | SMTP password |
| `SMTP_SENDER` | From address for outgoing email |
| `LISTMONK_CREDENTIALS` | Listmonk `user:pass` |
| `N8N_DB_PASSWORD` | n8n PostgreSQL password |
To set keys, create `/opt/websites/cosmolocal-website/.env` on the server and redeploy.
All credentials are stored in `.env` on the server (gitignored). See server admin for values.
## Workflows
@ -51,7 +56,7 @@ Syncs all CRM contacts with email addresses to Listmonk list #1. Adjust the list
**Trigger:** Weekly on Monday at 9:00 AM
**Flow:** Fetch contacts > Filter those not updated in 14+ days > Email report to team
Sends an HTML table report to `***REDACTED_EMAIL***` with stale contacts and a link to the CRM.
Sends an HTML table report to the team with stale contacts and a link to the CRM.
### 05 — Webhook Events (Git to CRM)
**Trigger:** Webhook POST to `/webhook/git-events`

View File

@ -455,10 +455,10 @@ export default function Home() {
communities, and impact-driven investors.
</p>
<a
href="mailto:***REDACTED_EMAIL***"
href="mailto:hello@cosmolocal.world"
className="mt-6 inline-block rounded-full bg-accent px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-accent-light"
>
***REDACTED_EMAIL***
hello@cosmolocal.world
</a>
</div>
</section>