diff --git a/docs/agents.html b/docs/agents.html index 51f21fd..aa376b1 100644 --- a/docs/agents.html +++ b/docs/agents.html @@ -206,8 +206,12 @@
Send emails via SMTP.
+Send emails via Resend API or SMTP, and receive inbound email webhooks.
Configure SMTP in ~/.weavr/config.yaml:
+Configure SMTP in ~/.weavr/config.yaml or set environment variables:
Configure your email provider to POST inbound email payloads to /webhook/email (or a custom path).
+trigger:
+ type: email.inbound
+ with:
+ path: email
+ provider: generic
+ Integrate CalDAV calendars (Nextcloud, iCloud, Fastmail, and more).
+ +- id: list-events
+ action: calendar.list_events
+ with:
+ calendarUrl: "https://cal.example.com/dav/calendars/user/default/"
+ username: "user@example.com"
+ password: "app-password"
+ from: "2026-02-07T00:00:00Z"
+ to: "2026-02-14T00:00:00Z"
+ - id: create-event
+ action: calendar.create_event
+ with:
+ calendarUrl: "https://cal.example.com/dav/calendars/user/default/"
+ username: "user@example.com"
+ password: "app-password"
+ summary: "Weekly Sync"
+ start: "2026-02-10T15:00:00Z"
+ end: "2026-02-10T15:30:00Z"
+ trigger:
+ type: calendar.event_upcoming
+ with:
+ calendarUrl: "https://cal.example.com/dav/calendars/user/default/"
+ username: "user@example.com"
+ password: "app-password"
+ windowMinutes: 60
+ pollIntervalSeconds: 60
Hello...
' }, + { name: 'provider', label: 'Provider', type: 'select', options: [ + { value: 'auto', label: 'Auto' }, + { value: 'smtp', label: 'SMTP' }, + { value: 'api', label: 'API' }, + ], default: 'auto' }, + ], + }, + // Calendar (CalDAV) + { + id: 'calendar.list_events', + label: 'List Events', + description: 'List events from a CalDAV calendar', + icon: 'calendar', + category: 'Calendar', + fields: [ + { name: 'calendarUrl', label: 'Calendar URL', type: 'text', placeholder: 'https://cal.example.com/dav/calendars/user/default/', required: true }, + { name: 'username', label: 'Username', type: 'text', placeholder: 'user@example.com' }, + { name: 'password', label: 'Password', type: 'text', placeholder: 'App password' }, + { name: 'bearerToken', label: 'Bearer Token', type: 'text', placeholder: 'Bearer token (optional)' }, + { name: 'from', label: 'From', type: 'text', placeholder: '2026-02-07T00:00:00Z' }, + { name: 'to', label: 'To', type: 'text', placeholder: '2026-02-14T00:00:00Z' }, + { name: 'limit', label: 'Limit', type: 'number', placeholder: '10', default: 10 }, + ], + outputFields: [ + { name: 'count', type: 'number', description: 'Number of events returned' }, + { name: 'events', type: 'array', description: 'List of events' }, + ], + }, + { + id: 'calendar.create_event', + label: 'Create Event', + description: 'Create or update a CalDAV event', + icon: 'calendar', + category: 'Calendar', + fields: [ + { name: 'calendarUrl', label: 'Calendar URL', type: 'text', placeholder: 'https://cal.example.com/dav/calendars/user/default/', required: true }, + { name: 'username', label: 'Username', type: 'text', placeholder: 'user@example.com' }, + { name: 'password', label: 'Password', type: 'text', placeholder: 'App password' }, + { name: 'bearerToken', label: 'Bearer Token', type: 'text', placeholder: 'Bearer token (optional)' }, + { name: 'summary', label: 'Summary', type: 'text', placeholder: 'Weekly sync', required: true }, + { name: 'description', label: 'Description', type: 'textarea', placeholder: 'Agenda...' }, + { name: 'location', label: 'Location', type: 'text', placeholder: 'Conference room' }, + { name: 'start', label: 'Start', type: 'text', placeholder: '2026-02-10T15:00:00Z', required: true }, + { name: 'end', label: 'End', type: 'text', placeholder: '2026-02-10T15:30:00Z' }, + { name: 'allDay', label: 'All Day', type: 'boolean' }, + { name: 'timezone', label: 'Timezone', type: 'text', placeholder: 'America/New_York' }, + ], + outputFields: [ + { name: 'uid', type: 'string', description: 'Event UID' }, + { name: 'url', type: 'string', description: 'Event URL' }, + { name: 'status', type: 'number', description: 'HTTP status' }, + ], + }, + { + id: 'calendar.delete_event', + label: 'Delete Event', + description: 'Delete a CalDAV event', + icon: 'calendar', + category: 'Calendar', + fields: [ + { name: 'calendarUrl', label: 'Calendar URL', type: 'text', placeholder: 'https://cal.example.com/dav/calendars/user/default/', required: true }, + { name: 'username', label: 'Username', type: 'text', placeholder: 'user@example.com' }, + { name: 'password', label: 'Password', type: 'text', placeholder: 'App password' }, + { name: 'bearerToken', label: 'Bearer Token', type: 'text', placeholder: 'Bearer token (optional)' }, + { name: 'uid', label: 'Event UID', type: 'text', placeholder: 'event-uid', required: true }, + ], + outputFields: [ + { name: 'uid', type: 'string', description: 'Event UID' }, + { name: 'status', type: 'number', description: 'HTTP status' }, ], }, // JSON @@ -576,6 +651,17 @@ const TRIGGER_SCHEMAS: ActionSchema[] = [ ], default: 'POST' }, ], }, + { + id: 'email.inbound', + label: 'Inbound Email', + description: 'Trigger on inbound email webhooks', + icon: 'email', + category: 'Email', + fields: [ + { name: 'path', label: 'Webhook Path', type: 'text', placeholder: 'email', required: true, default: 'email' }, + { name: 'provider', label: 'Provider', type: 'text', placeholder: 'resend (optional)' }, + ], + }, { id: 'cron.schedule', label: 'Schedule', @@ -587,6 +673,22 @@ const TRIGGER_SCHEMAS: ActionSchema[] = [ { name: 'timezone', label: 'Timezone', type: 'select', options: TIMEZONE_OPTIONS, default: '' }, ], }, + { + id: 'calendar.event_upcoming', + label: 'Calendar Event', + description: 'Trigger when a CalDAV event is upcoming', + icon: 'calendar', + category: 'Calendar', + fields: [ + { name: 'calendarUrl', label: 'Calendar URL', type: 'text', placeholder: 'https://cal.example.com/dav/calendars/user/default/', required: true }, + { name: 'username', label: 'Username', type: 'text', placeholder: 'user@example.com' }, + { name: 'password', label: 'Password', type: 'text', placeholder: 'App password' }, + { name: 'bearerToken', label: 'Bearer Token', type: 'text', placeholder: 'Bearer token (optional)' }, + { name: 'windowMinutes', label: 'Window Minutes', type: 'number', placeholder: '60', default: 60 }, + { name: 'pollIntervalSeconds', label: 'Poll Interval (seconds)', type: 'number', placeholder: '60', default: 60 }, + { name: 'lookbackMinutes', label: 'Lookback Minutes', type: 'number', placeholder: '5', default: 5 }, + ], + }, { id: 'github.push', label: 'GitHub Push', @@ -843,6 +945,29 @@ function getTriggerVariables(triggerType?: string): Array<{ path: string; descri ]; } + if (triggerType?.startsWith('email.inbound')) { + return [ + ...common, + { path: 'trigger.path', description: 'Webhook path' }, + { path: 'trigger.provider', description: 'Provider identifier' }, + { path: 'trigger.data.body', description: 'Inbound payload body' }, + { path: 'trigger.data.headers', description: 'Inbound payload headers' }, + ]; + } + + if (triggerType?.startsWith('calendar.event_upcoming')) { + return [ + ...common, + { path: 'trigger.calendarUrl', description: 'Calendar URL' }, + { path: 'trigger.event.summary', description: 'Event summary' }, + { path: 'trigger.event.start', description: 'Event start time' }, + { path: 'trigger.event.end', description: 'Event end time' }, + { path: 'trigger.event.location', description: 'Event location' }, + { path: 'trigger.windowMinutes', description: 'Window minutes' }, + { path: 'trigger.fetchedAt', description: 'Fetch timestamp' }, + ]; + } + // Generic fallback return [ ...common, diff --git a/src/web/app/pages/Settings.tsx b/src/web/app/pages/Settings.tsx index b59ef03..0bdc7d0 100644 --- a/src/web/app/pages/Settings.tsx +++ b/src/web/app/pages/Settings.tsx @@ -55,6 +55,27 @@ interface Config { host: string; }; timezone?: string; + email?: { + smtp?: { + host?: string; + port?: number; + secure?: boolean; + user?: string; + pass?: string; + authMethod?: 'login' | 'plain'; + hasPass?: boolean; + }; + }; + calendar?: { + caldav?: { + calendarUrl?: string; + username?: string; + password?: string; + bearerToken?: string; + hasPassword?: boolean; + hasBearerToken?: boolean; + }; + }; ai?: { provider?: string; model?: string; @@ -122,6 +143,24 @@ export function Settings() { const [whatsappConnecting, setWhatsappConnecting] = useState(false); const [whatsappStatus, setWhatsappStatus] = useState<'disconnected' | 'connecting' | 'connected'>('disconnected'); + // Email SMTP state + const [smtpHost, setSmtpHost] = useState(''); + const [smtpPort, setSmtpPort] = useState(''); + const [smtpSecure, setSmtpSecure] = useState(false); + const [smtpUser, setSmtpUser] = useState(''); + const [smtpPass, setSmtpPass] = useState(''); + const [showSmtpPass, setShowSmtpPass] = useState(false); + const [smtpAuthMethod, setSmtpAuthMethod] = useState<'login' | 'plain'>('login'); + + // Calendar (CalDAV) state + const [caldavUrl, setCaldavUrl] = useState(''); + const [caldavUsername, setCaldavUsername] = useState(''); + const [caldavPassword, setCaldavPassword] = useState(''); + const [showCaldavPassword, setShowCaldavPassword] = useState(false); + const [caldavBearer, setCaldavBearer] = useState(''); + const [showCaldavBearer, setShowCaldavBearer] = useState(false); + const [caldavAuthMode, setCaldavAuthMode] = useState<'basic' | 'bearer'>('basic'); + // Timezone - detect system default const systemTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -143,11 +182,31 @@ export function Settings() { ]}, ]; + const applyEmailCalendarConfig = (nextConfig: Config | null) => { + if (!nextConfig) return; + + const smtp = nextConfig.email?.smtp; + setSmtpHost(smtp?.host ?? ''); + setSmtpPort(smtp?.port ? String(smtp.port) : ''); + setSmtpSecure(Boolean(smtp?.secure)); + setSmtpUser(smtp?.user ?? ''); + setSmtpAuthMethod(smtp?.authMethod ?? 'login'); + setSmtpPass(''); + + const caldav = nextConfig.calendar?.caldav; + setCaldavUrl(caldav?.calendarUrl ?? ''); + setCaldavUsername(caldav?.username ?? ''); + setCaldavAuthMode(caldav?.bearerToken ? 'bearer' : 'basic'); + setCaldavPassword(''); + setCaldavBearer(''); + }; + useEffect(() => { fetch('/api/config') .then((res) => res.json()) .then((data) => { setConfig(data.config); + applyEmailCalendarConfig(data.config); setLoading(false); }) .catch((err) => { @@ -271,6 +330,31 @@ export function Settings() { setMessage(null); try { + const smtpPortValue = smtpPort ? parseInt(smtpPort, 10) : undefined; + const smtpConfig = (smtpHost || smtpUser || smtpPass || smtpPort || config?.email?.smtp) ? { + host: smtpHost, + port: smtpPortValue, + secure: smtpSecure, + user: smtpUser || undefined, + pass: smtpPass || undefined, + authMethod: smtpAuthMethod, + } : undefined; + + const caldavConfig = (caldavUrl || caldavUsername || caldavPassword || caldavBearer || config?.calendar?.caldav) ? { + calendarUrl: caldavUrl, + ...(caldavAuthMode === 'basic' + ? { + username: caldavUsername || undefined, + password: caldavPassword || undefined, + bearerToken: undefined, + } + : { + username: undefined, + password: undefined, + bearerToken: caldavBearer || undefined, + }), + } : undefined; + const saveConfig = { ...config, ai: config.ai?.provider ? { @@ -281,6 +365,8 @@ export function Settings() { provider: 'brave', apiKey: braveApiKey, } : config.webSearch, + email: smtpConfig ? { smtp: smtpConfig } : config.email, + calendar: caldavConfig ? { caldav: caldavConfig } : config.calendar, messaging: { ...config.messaging, telegram: (telegramToken || telegramChatId) ? { @@ -303,10 +389,14 @@ export function Settings() { setBraveApiKey(''); // Clear the Brave API key field after saving setTelegramToken(''); // Clear the telegram token field after saving setTelegramChatId(''); // Clear the telegram chat ID field after saving + setSmtpPass(''); + setCaldavPassword(''); + setCaldavBearer(''); // Reload config to get updated hasApiKey status const reloadRes = await fetch('/api/config'); const reloadData = await reloadRes.json(); setConfig(reloadData.config); + applyEmailCalendarConfig(reloadData.config); } else { const data = await response.json(); setMessage({ type: 'error', text: data.error ?? 'Failed to save settings' }); @@ -494,6 +584,7 @@ export function Settings() { }; const isMacOS = navigator.platform.toLowerCase().includes('mac'); + const webhookBaseUrl = typeof window !== 'undefined' ? window.location.origin : ''; return (
+ Configure SMTP credentials for email.send actions. You can also use API keys in workflows.
+
smtp or auto
+ {webhookBaseUrl}/webhook/email
+
+ Configure CalDAV access for calendar.* actions and triggers.
+