1
<html>
2
+
3
<head>
4
<title>Email Integration</title>
5
+ <script type="module">
6
+ import { store } from "/plugins/_email_integration/webui/email-config-store.js";
7
+ </script>
8
</head>
9
10
<body>
7
- <div x-data="{
8
- get handlers() { return config?.handlers || [] },
9
- editing: null,
10
- testing: null,
11
- test_results: null,
12
- projects: [],
13
- async init() {
14
- try {
15
- const { callJsonApi } = await import('/js/api.js');
16
- const res = await callJsonApi('projects', { action: 'list' });
17
- this.projects = res.data || [];
18
- } catch (e) { this.projects = []; }
19
- },
20
- add_handler() {
21
- if (!config.handlers) config.handlers = [];
22
- config.handlers.push({
23
- name: 'handler_' + (config.handlers.length + 1),
24
- enabled: false,
25
- account_type: 'imap',
26
- imap_server: '',
27
- imap_port: 993,
28
- smtp_server: '',
29
- smtp_port: 587,
30
- username: '',
31
- password: '',
32
- poll_mode: 'seconds',
33
- poll_interval_seconds: 15,
34
- poll_interval_cron: '*/2 * * * *',
35
- process_unread_days: 0,
36
- sender_whitelist: [],
37
- project: '',
38
- dispatcher_model: 'utility',
39
- dispatcher_instructions: '',
40
- agent_instructions: ''
41
- });
42
- this.editing = config.handlers.length - 1;
43
- },
44
- remove_handler(idx) {
45
- config.handlers.splice(idx, 1);
46
- this.editing = null;
47
- },
48
- whitelist_text(handler) {
49
- return (handler.sender_whitelist || []).join(', ');
50
- },
51
- set_whitelist(handler, val) {
52
- handler.sender_whitelist = val.split(',').map(s => s.trim()).filter(s => s);
53
- },
54
- async test_connection(idx) {
55
- this.testing = idx;
56
- this.test_results = null;
57
- try {
58
- const { callJsonApi } = await import('/js/api.js');
59
- const res = await callJsonApi('/plugins/_email_integration/test_connection', {
60
- handler: this.handlers[idx]
61
- });
62
- this.test_results = res;
63
- } catch (e) {
64
- this.test_results = { success: false, results: [{ test: 'Connection', ok: false, message: String(e) }] };
65
- }
66
- this.testing = null;
67
- }
68
- }">
11
+ <div x-data x-init="$store.emailConfig.init(config, context)" x-destroy="$store.emailConfig.cleanup()">
12
<template x-if="config">
70
- <div>
71
- <div class="section-title">Email Integration</div>
72
- <div class="section-description">
73
- Configure email handlers to communicate with Agent Zero via email.
74
- Each handler connects to an email account and polls for new messages.
75
- </div>
76
-
77
- <!-- Handler list -->
78
- <template x-for="(handler, idx) in handlers" :key="idx">
79
- <div style="border: 1px solid var(--border-color, #333); border-radius: 8px; padding: 12px; margin-bottom: 8px; margin-top: 8px;">
80
-
81
- <!-- Header row -->
82
- <div style="display: flex; justify-content: space-between; align-items: center; cursor: pointer;"
83
- @click="editing = editing === idx ? null : idx">
84
- <div>
85
- <span style="font-weight: bold;" x-text="handler.name"></span>
86
- <span style="opacity: 0.6; margin-left: 8px;" x-text="handler.enabled ? 'Enabled' : 'Disabled'"></span>
13
+ <div class="email-settings">
14
+ <div class="section-title">Email</div>
15
+
16
+ <template x-if="$store.emailConfig.didInit && $store.emailConfig.handlers.length === 0">
17
+ <div class="email-empty">
18
+ <div class="email-empty-title">Connect Agent Zero and your email account</div>
19
+ <div class="email-empty-copy">
20
+ Most people only need a provider, an email address, and an app password.
21
+ </div>
22
+ <button class="btn btn-field" @click="$store.emailConfig.addHandler()">
23
+ Connect
24
+ </button>
25
+ </div>
26
+ </template>
27
+
28
+ <template x-for="(handler, idx) in $store.emailConfig.handlers" :key="idx">
29
+ <div class="email-card">
30
+ <div class="email-card-header" @click="$store.emailConfig.toggleEditing(idx)">
31
+ <div class="email-card-heading">
32
+ <div class="email-card-title" x-text="$store.emailConfig.handlerTitle(handler, idx)">
33
+ </div>
34
+ <div class="email-card-subtitle" x-text="$store.emailConfig.handlerSubtitle(handler)">
35
+ </div>
36
+ </div>
37
+ <div class="email-card-actions">
38
+ <span class="email-status-pill"
39
+ :class="'tone-' + $store.emailConfig.statusTone(handler)"
40
+ x-text="$store.emailConfig.statusLabel(handler)"></span>
41
+ <button class="btn btn-action delete"
42
+ @click.stop="$confirmClick($event, () => $store.emailConfig.removeHandler(idx))"
43
+ title="Remove inbox">
44
+ <span class="material-symbols-outlined">delete</span>
45
+ </button>
46
+ <span class="material-symbols-outlined email-card-chevron"
47
+ :class="{ 'is-open': $store.emailConfig.editing === idx }">expand_more</span>
48
</div>
88
- <button class="btn btn-action delete" @click.stop="$confirmClick($event, () => remove_handler(idx))" title="Remove handler">
89
- <span class="material-symbols-outlined">delete</span>
90
- </button>
49
</div>
50
93
- <!-- Expanded editor -->
94
- <template x-if="editing === idx">
95
- <div style="margin-top: 16px;">
51
+ <template x-if="$store.emailConfig.editing === idx">
52
+ <div class="email-card-body">
53
+ <div class="email-intro-copy">
54
+ <div x-text="$store.emailConfig.providerHint(handler)"></div>
55
+ <template x-if="$store.emailConfig.providerHelpUrl(handler)">
56
+ <a class="email-intro-link"
57
+ :href="$store.emailConfig.providerHelpUrl(handler)"
58
+ target="_blank"
59
+ rel="noopener"
60
+ x-text="$store.emailConfig.providerHelpLabel(handler)"></a>
61
+ </template>
62
+ </div>
63
64
<div class="field">
65
<div class="field-label">
99
- <div class="field-title">Enabled</div>
100
- <div class="field-description">Enable or disable inbox polling for this handler</div>
66
+ <div class="field-title">Turn on this inbox</div>
67
+ <div class="field-description">Enable when you are ready for Agent Zero to start
68
+ checking mail.</div>
69
</div>
70
<div class="field-control">
71
<label class="toggle">
77
78
<div class="field">
79
<div class="field-label">
112
- <div class="field-title">Handler Name</div>
113
- <div class="field-description">Unique identifier for this email handler</div>
80
+ <div class="field-title">Provider</div>
81
+ <div class="field-description">Pick the provider first. We will fill in the
82
+ common server settings for you.</div>
83
</div>
84
<div class="field-control">
116
- <input type="text" x-model="handler.name" placeholder="e.g. support" />
117
- </div>
118
- </div>
119
-
120
- <div class="field">
121
- <div class="field-label">
122
- <div class="field-title">Account Type</div>
123
- <div class="field-description">IMAP for most providers, Exchange for Microsoft Exchange/Office 365</div>
124
- </div>
125
- <div class="field-control">
126
- <select x-model="handler.account_type">
127
- <option value="imap">IMAP</option>
85
+ <select :value="$store.emailConfig.providerValue(handler)"
86
+ @change="$store.emailConfig.applyProvider(handler, $event.target.value)">
87
+ <option value="">Choose a provider</option>
88
+ <option value="gmail">Gmail</option>
89
+ <option value="icloud">iCloud Mail</option>
90
+ <option value="microsoft365">Outlook / Microsoft 365</option>
91
+ <option value="yahoo">Yahoo Mail</option>
92
<option value="exchange">Exchange</option>
93
+ <option value="custom-imap">Custom IMAP</option>
94
</select>
95
</div>
96
</div>
97
98
<div class="field">
99
<div class="field-label">
135
- <div class="field-title">IMAP Server</div>
136
- <div class="field-description">Incoming mail server hostname</div>
100
+ <div class="field-title">Email address</div>
101
+ <div class="field-description">Usually the full address for this inbox.</div>
102
</div>
103
<div class="field-control">
139
- <input type="text" x-model="handler.imap_server" placeholder="imap.gmail.com" />
140
- </div>
141
- </div>
142
-
143
- <div class="field">
144
- <div class="field-label">
145
- <div class="field-title">IMAP Port</div>
146
- <div class="field-description">SSL port for incoming mail, usually 993</div>
147
- </div>
148
- <div class="field-control">
149
- <input type="number" x-model.number="handler.imap_port" placeholder="993" />
150
- </div>
151
- </div>
152
-
153
- <div class="field">
154
- <div class="field-label">
155
- <div class="field-title">SMTP Server</div>
156
- <div class="field-description">Outgoing mail server hostname. Defaults to IMAP server if empty</div>
157
- </div>
158
- <div class="field-control">
159
- <input type="text" x-model="handler.smtp_server" placeholder="smtp.gmail.com" />
160
- </div>
161
- </div>
162
-
163
- <div class="field">
164
- <div class="field-label">
165
- <div class="field-title">SMTP Port</div>
166
- <div class="field-description">TLS port for outgoing mail, usually 587</div>
167
- </div>
168
- <div class="field-control">
169
- <input type="number" x-model.number="handler.smtp_port" placeholder="587" />
170
- </div>
171
- </div>
172
-
173
- <div class="field">
174
- <div class="field-label">
175
- <div class="field-title">Username</div>
176
- <div class="field-description">Email account login, usually the full email address</div>
177
- </div>
178
- <div class="field-control">
179
- <input type="text" x-model="handler.username" placeholder="user@domain.com" />
104
+ <input type="text" x-model="handler.username"
105
+ @input="$store.emailConfig.maybeAutoname(handler)"
106
+ placeholder="name@company.com" />
107
</div>
108
</div>
109
110
<div class="field">
111
<div class="field-label">
112
<div class="field-title">Password</div>
186
- <div class="field-description">Account password or app-specific password</div>
113
+ <div class="field-description">An app password is best. Many mail providers
114
+ block regular account passwords.</div>
115
</div>
116
<div class="field-control">
117
<input type="password" x-model="handler.password" />
118
</div>
119
</div>
120
193
- <div class="field">
194
- <div class="field-label">
195
- <div class="field-title">Poll Mode</div>
196
- <div class="field-description">How to schedule inbox checks</div>
197
- </div>
198
- <div class="field-control">
199
- <select x-model="handler.poll_mode">
200
- <option value="seconds">Interval (seconds)</option>
201
- <option value="cron">Cron expression</option>
202
- </select>
121
+ <template x-if="$store.emailConfig.showExchangeServer(handler)">
122
+ <div>
123
+ <div class="field">
124
+ <div class="field-label">
125
+ <div class="field-title"
126
+ x-text="$store.emailConfig.incomingLabel(handler)"></div>
127
+ <div class="field-description"
128
+ x-text="$store.emailConfig.incomingDescription(handler)"></div>
129
+ </div>
130
+ <div class="field-control">
131
+ <input type="text" x-model="handler.imap_server"
132
+ :placeholder="$store.emailConfig.incomingPlaceholder(handler)" />
133
+ </div>
134
+ </div>
135
</div>
204
- </div>
136
+ </template>
137
206
- <div class="field" x-show="handler.poll_mode === 'seconds'">
207
- <div class="field-label">
208
- <div class="field-title">Poll Interval (seconds)</div>
209
- <div class="field-description">How often to check for new emails</div>
210
- </div>
211
- <div class="field-control">
212
- <input type="number" x-model.number="handler.poll_interval_seconds" min="5" placeholder="15" />
213
- </div>
214
- </div>
138
+ <template x-if="$store.emailConfig.showManualServers(handler)">
139
+ <div class="email-grid">
140
+ <div class="field">
141
+ <div class="field-label">
142
+ <div class="field-title">Incoming mail server</div>
143
+ <div class="field-description">The IMAP server for this inbox.</div>
144
+ </div>
145
+ <div class="field-control">
146
+ <input type="text" x-model="handler.imap_server"
147
+ placeholder="imap.your-provider.com" />
148
+ </div>
149
+ </div>
150
216
- <div class="field" x-show="handler.poll_mode !== 'seconds'">
217
- <div class="field-label">
218
- <div class="field-title">Cron Expression</div>
219
- <div class="field-description">e.g. */2 * * * * for every 2 minutes</div>
220
- </div>
221
- <div class="field-control">
222
- <input type="text" x-model="handler.poll_interval_cron" placeholder="*/2 * * * *" />
223
- </div>
224
- </div>
151
+ <div class="field">
152
+ <div class="field-label">
153
+ <div class="field-title">Incoming port</div>
154
+ <div class="field-description">993 is the usual SSL port.</div>
155
+ </div>
156
+ <div class="field-control">
157
+ <input type="number" x-model.number="handler.imap_port"
158
+ placeholder="993" />
159
+ </div>
160
+ </div>
161
226
- <div class="field">
227
- <div class="field-label">
228
- <div class="field-title">Process Unread (days)</div>
229
- <div class="field-description">Process unread emails from the last N days on every startup. 0 = only track new emails</div>
230
- </div>
231
- <div class="field-control">
232
- <input type="number" x-model.number="handler.process_unread_days" min="0" placeholder="0" />
233
- </div>
234
- </div>
162
+ <div class="field">
163
+ <div class="field-label">
164
+ <div class="field-title">Outgoing mail server</div>
165
+ <div class="field-description">The SMTP server used for replies.</div>
166
+ </div>
167
+ <div class="field-control">
168
+ <input type="text" x-model="handler.smtp_server"
169
+ placeholder="smtp.your-provider.com" />
170
+ </div>
171
+ </div>
172
236
- <div class="field">
237
- <div class="field-label">
238
- <div class="field-title">Sender Whitelist</div>
239
- <div class="field-description">Comma-separated. Empty = allow all. Wildcards supported (e.g. *@company.com)</div>
240
- </div>
241
- <div class="field-control">
242
- <input type="text" :value="whitelist_text(handler)" @input="set_whitelist(handler, $event.target.value)" placeholder="*@company.com, boss@other.com" />
173
+ <div class="field">
174
+ <div class="field-label">
175
+ <div class="field-title">Outgoing port</div>
176
+ <div class="field-description">587 is the usual TLS port.</div>
177
+ </div>
178
+ <div class="field-control">
179
+ <input type="number" x-model.number="handler.smtp_port"
180
+ placeholder="587" />
181
+ </div>
182
+ </div>
183
</div>
244
- </div>
184
+ </template>
185
246
- <div class="field">
247
- <div class="field-label">
248
- <div class="field-title">Project</div>
249
- <div class="field-description">Project to activate for email chats</div>
186
+ <template
187
+ x-if="!$store.emailConfig.showManualServers(handler) && !$store.emailConfig.showExchangeServer(handler) && $store.emailConfig.providerValue(handler)">
188
+ <div class="email-note">
189
+ Server settings are filled in for <span
190
+ x-text="$store.emailConfig.providerLabel($store.emailConfig.providerValue(handler))"></span>.
191
+ You can change them any time in Advanced.
192
</div>
251
- <div class="field-control">
252
- <select :value="handler.project" @change="handler.project = $event.target.value">
253
- <option value="">No project</option>
254
- <template x-for="proj in projects" :key="proj.name">
255
- <option :value="proj.name" x-text="proj.title || proj.name" :selected="handler.project === proj.name"></option>
256
- </template>
257
- </select>
258
- </div>
259
- </div>
193
+ </template>
194
261
- <div class="field">
262
- <div class="field-label">
263
- <div class="field-title">Dispatcher Model</div>
264
- <div class="field-description">LLM model used to route incoming emails to chats. Utility is faster, chat is more capable</div>
265
- </div>
266
- <div class="field-control">
267
- <select x-model="handler.dispatcher_model">
268
- <option value="utility">Utility</option>
269
- <option value="chat">Chat</option>
270
- </select>
271
- </div>
195
+ <div class="email-missing" x-show="$store.emailConfig.missingBits(handler).length > 0">
196
+ Still needed:
197
+ <span x-text="$store.emailConfig.missingBits(handler).join(', ')"></span>
198
</div>
199
200
<div class="field">
201
<div class="field-label">
276
- <div class="field-title">Dispatcher Instructions</div>
277
- <div class="field-description">Extra instructions for the AI that routes emails to chats</div>
202
+ <div class="field-title">Routing instructions</div>
203
+ <div class="field-description">Extra guidance for how incoming email should be
204
+ routed into chats.</div>
205
</div>
206
<div class="field-control">
280
- <textarea x-model="handler.dispatcher_instructions" rows="3" placeholder="e.g. Always start a new chat for emails from support@..."></textarea>
207
+ <textarea x-model="handler.dispatcher_instructions" rows="3"
208
+ placeholder="Always start a new chat for invoices or billing questions."></textarea>
209
</div>
210
</div>
211
212
<div class="field">
213
<div class="field-label">
286
- <div class="field-title">Agent Instructions</div>
287
- <div class="field-description">Extra instructions for the agent in email chats</div>
214
+ <div class="field-title">Reply instructions</div>
215
+ <div class="field-description">Extra guidance for the agent when it replies by
216
+ email.</div>
217
</div>
218
<div class="field-control">
290
- <textarea x-model="handler.agent_instructions" rows="3" placeholder="e.g. Always respond in formal English..."></textarea>
219
+ <textarea x-model="handler.agent_instructions" rows="3"
220
+ placeholder="Reply in a calm, concise tone."></textarea>
221
</div>
222
</div>
223
294
- <!-- Test connection -->
295
- <div style="margin-top: 12px; display: flex; align-items: center; gap: 12px;">
296
- <button class="btn btn-field" @click.stop="test_connection(idx)"
297
- :disabled="testing === idx">
298
- <span x-show="testing !== idx">Test Connection</span>
299
- <span x-show="testing === idx">Testing...</span>
224
+ <div class="email-test-panel">
225
+ <div class="email-test-copy">
226
+ <div class="email-test-title">Check the connection</div>
227
+ <div class="email-test-description"
228
+ x-text="$store.emailConfig.testIntro(handler)"></div>
229
+ </div>
230
+ <button class="btn btn-field" @click.stop="$store.emailConfig.testConnection(idx)"
231
+ :disabled="$store.emailConfig.testing === idx || !$store.emailConfig.canTest(handler)">
232
+ <span x-text="$store.emailConfig.testButtonLabel(handler, idx)"></span>
233
</button>
234
</div>
235
303
- <!-- Test results -->
304
- <template x-if="test_results && editing === idx">
305
- <div style="margin-top: 8px; padding: 8px 12px; border-radius: 6px; font-size: 0.85rem;
306
- border: 1px solid var(--border-color, #333);">
307
- <template x-for="r in test_results.results" :key="r.test">
308
- <div style="display: flex; align-items: center; gap: 8px; padding: 4px 0;">
309
- <span x-text="r.ok ? '✓' : '✗'"
310
- :style="'font-weight: bold; color:' + (r.ok ? '#4caf50' : '#f44336')"></span>
311
- <span style="font-weight: 500; min-width: 50px;" x-text="r.test"></span>
312
- <span style="opacity: 0.8;" x-text="r.message"></span>
236
+ <template
237
+ x-if="$store.emailConfig.testResults && $store.emailConfig.testResultsFor === idx">
238
+ <div class="email-results"
239
+ :class="{ 'is-error': !$store.emailConfig.testResults.success }">
240
+ <template x-for="result in $store.emailConfig.testResults.results"
241
+ :key="result.test + result.message">
242
+ <div class="email-result-row">
243
+ <span class="email-result-icon" x-text="result.ok ? '✓' : '✗'"></span>
244
+ <div class="email-result-copy">
245
+ <div class="email-result-title"
246
+ x-text="$store.emailConfig.resultTitle(result)"></div>
247
+ <div class="email-result-message"
248
+ x-text="$store.emailConfig.resultMessage(result)"></div>
249
+ </div>
250
</div>
251
</template>
252
</div>
253
</template>
254
255
+ <details class="email-advanced">
256
+ <summary>
257
+ <span>Advanced</span>
258
+ <span class="material-symbols-outlined email-advanced-chevron"
259
+ aria-hidden="true">keyboard_arrow_down</span>
260
+ </summary>
261
+ <div class="email-advanced-body">
262
+ <div class="field">
263
+ <div class="field-label">
264
+ <div class="field-title">Inbox name</div>
265
+ <div class="field-description">A friendly internal label for this
266
+ connection.</div>
267
+ </div>
268
+ <div class="field-control">
269
+ <input type="text" x-model="handler.name" placeholder="support" />
270
+ </div>
271
+ </div>
272
+
273
+ <div class="email-grid">
274
+ <div class="field">
275
+ <div class="field-label">
276
+ <div class="field-title">Incoming mail server</div>
277
+ <div class="field-description">Override the auto-filled incoming
278
+ server if needed.</div>
279
+ </div>
280
+ <div class="field-control">
281
+ <input type="text" x-model="handler.imap_server"
282
+ placeholder="imap.your-provider.com" />
283
+ </div>
284
+ </div>
285
+
286
+ <div class="field">
287
+ <div class="field-label">
288
+ <div class="field-title">Incoming port</div>
289
+ <div class="field-description">993 is the common SSL port.</div>
290
+ </div>
291
+ <div class="field-control">
292
+ <input type="number" x-model.number="handler.imap_port"
293
+ placeholder="993" />
294
+ </div>
295
+ </div>
296
+
297
+ <div class="field">
298
+ <div class="field-label">
299
+ <div class="field-title">Outgoing mail server</div>
300
+ <div class="field-description">Override the auto-filled reply server
301
+ if needed.</div>
302
+ </div>
303
+ <div class="field-control">
304
+ <input type="text" x-model="handler.smtp_server"
305
+ placeholder="smtp.your-provider.com" />
306
+ </div>
307
+ </div>
308
+
309
+ <div class="field">
310
+ <div class="field-label">
311
+ <div class="field-title">Outgoing port</div>
312
+ <div class="field-description">587 is the common TLS port.</div>
313
+ </div>
314
+ <div class="field-control">
315
+ <input type="number" x-model.number="handler.smtp_port"
316
+ placeholder="587" />
317
+ </div>
318
+ </div>
319
+ </div>
320
+
321
+ <div class="field">
322
+ <div class="field-label">
323
+ <div class="field-title">Project</div>
324
+ <div class="field-description">Open email conversations inside a
325
+ specific project.</div>
326
+ </div>
327
+ <div class="field-control">
328
+ <select :value="handler.project"
329
+ @change="handler.project = $event.target.value">
330
+ <option value="">No project</option>
331
+ <template x-for="proj in $store.emailConfig.projects"
332
+ :key="proj.name">
333
+ <option :value="proj.name" x-text="proj.title || proj.name"
334
+ :selected="handler.project === proj.name"></option>
335
+ </template>
336
+ </select>
337
+ </div>
338
+ </div>
339
+
340
+ <div class="field">
341
+ <div class="field-label">
342
+ <div class="field-title">Allowed senders</div>
343
+ <div class="field-description">Leave empty to allow anyone. Wildcards
344
+ like *@company.com work.</div>
345
+ </div>
346
+ <div class="field-control">
347
+ <input type="text" :value="$store.emailConfig.whitelistText(handler)"
348
+ @input="$store.emailConfig.setWhitelist(handler, $event.target.value)"
349
+ placeholder="*@company.com, founder@company.com" />
350
+ </div>
351
+ </div>
352
+
353
+ <div class="field">
354
+ <div class="field-label">
355
+ <div class="field-title">Catch up on unread mail</div>
356
+ <div class="field-description">On startup, process unread mail from the
357
+ last N days. Use 0 for brand-new mail only.</div>
358
+ </div>
359
+ <div class="field-control">
360
+ <input type="number" x-model.number="handler.process_unread_days"
361
+ min="0" placeholder="0" />
362
+ </div>
363
+ </div>
364
+
365
+ <div class="field">
366
+ <div class="field-label">
367
+ <div class="field-title">Check for new mail</div>
368
+ <div class="field-description"
369
+ x-text="$store.emailConfig.frequencyHint(handler)"></div>
370
+ </div>
371
+ <div class="field-control">
372
+ <select :value="$store.emailConfig.frequencyValue(handler)"
373
+ @change="$store.emailConfig.applyFrequency(handler, $event.target.value)">
374
+ <option value="15">Every 15 seconds</option>
375
+ <option value="30">Every 30 seconds</option>
376
+ <option value="60">Every minute</option>
377
+ <option value="300">Every 5 minutes</option>
378
+ <option value="900">Every 15 minutes</option>
379
+ <option value="custom">Custom schedule</option>
380
+ </select>
381
+ </div>
382
+ </div>
383
+
384
+ <div class="field"
385
+ x-show="$store.emailConfig.frequencyValue(handler) === 'custom'">
386
+ <div class="field-label">
387
+ <div class="field-title">Scheduling mode</div>
388
+ <div class="field-description">Use seconds for a simple interval, or
389
+ switch to cron for full control.</div>
390
+ </div>
391
+ <div class="field-control">
392
+ <select x-model="handler.poll_mode">
393
+ <option value="seconds">Seconds</option>
394
+ <option value="cron">Cron</option>
395
+ </select>
396
+ </div>
397
+ </div>
398
+
399
+ <div class="field"
400
+ x-show="$store.emailConfig.frequencyValue(handler) === 'custom' && handler.poll_mode === 'seconds'">
401
+ <div class="field-label">
402
+ <div class="field-title">Poll interval (seconds)</div>
403
+ <div class="field-description">The exact delay between inbox checks.
404
+ </div>
405
+ </div>
406
+ <div class="field-control">
407
+ <input type="number" x-model.number="handler.poll_interval_seconds"
408
+ min="5" placeholder="60" />
409
+ </div>
410
+ </div>
411
+
412
+ <div class="field"
413
+ x-show="$store.emailConfig.frequencyValue(handler) === 'custom' && handler.poll_mode === 'cron'">
414
+ <div class="field-label">
415
+ <div class="field-title">Cron expression</div>
416
+ <div class="field-description">For example, */2 * * * * checks every two
417
+ minutes.</div>
418
+ </div>
419
+ <div class="field-control">
420
+ <input type="text" x-model="handler.poll_interval_cron"
421
+ placeholder="*/2 * * * *" />
422
+ </div>
423
+ </div>
424
+
425
+ <div class="field">
426
+ <div class="field-label">
427
+ <div class="field-title">Routing model</div>
428
+ <div class="field-description">Utility is faster. Chat is more capable
429
+ when routing gets nuanced.</div>
430
+ </div>
431
+ <div class="field-control">
432
+ <select x-model="handler.dispatcher_model">
433
+ <option value="utility">Utility</option>
434
+ <option value="chat">Chat</option>
435
+ </select>
436
+ </div>
437
+ </div>
438
+
439
+ </div>
440
+ </details>
441
</div>
442
</template>
443
</div>
444
</template>
445
323
- <button class="btn btn-field" @click="add_handler()" style="margin-top: 8px;">
324
- Add Handler
325
- </button>
446
+ <template x-if="$store.emailConfig.handlers.length > 0">
447
+ <button class="btn btn-field email-add-another" @click="$store.emailConfig.addHandler()">
448
+ Connect another inbox
449
+ </button>
450
+ </template>
451
</div>
452
</template>
453
</div>
454
+
455
+ <style>
456
+ .email-settings {
457
+ display: flex;
458
+ flex-direction: column;
459
+ gap: 0.9rem;
460
+ }
461
+
462
+ .email-advanced summary {
463
+ cursor: pointer;
464
+ list-style: none;
465
+ font-weight: 700;
466
+ display: flex;
467
+ align-items: center;
468
+ gap: 0.75rem;
469
+ }
470
+
471
+ .email-advanced summary::-webkit-details-marker {
472
+ display: none;
473
+ }
474
+
475
+ .email-advanced summary {
476
+ padding: 0.95rem 1rem;
477
+ }
478
+
479
+ .email-advanced-chevron {
480
+ margin-left: auto;
481
+ flex: 0 0 auto;
482
+ transition: transform 0.18s ease, opacity 0.18s ease;
483
+ opacity: 0.72;
484
+ }
485
+
486
+ .email-advanced[open] .email-advanced-chevron {
487
+ transform: rotate(180deg);
488
+ opacity: 1;
489
+ }
490
+
491
+ .email-guide-card {
492
+ border: 1px solid color-mix(in srgb, var(--color-border) 80%, white 20%);
493
+ border-radius: 12px;
494
+ padding: 0.9rem 1rem;
495
+ background: color-mix(in srgb, var(--color-background) 88%, white 12%);
496
+ }
497
+
498
+ .email-guide-title {
499
+ font-weight: 700;
500
+ margin-bottom: 0.55rem;
501
+ }
502
+
503
+ .email-guide-list {
504
+ margin: 0;
505
+ padding-left: 1.1rem;
506
+ color: var(--color-text-secondary);
507
+ line-height: 1.55;
508
+ }
509
+
510
+ .email-empty {
511
+ border-radius: 0.5rem;
512
+ padding: 1rem;
513
+ background: color-mix(in srgb, var(--color-background) 94%, white 6%);
514
+ }
515
+
516
+ .email-empty-title {
517
+ font-size: 1.05rem;
518
+ font-weight: 700;
519
+ }
520
+
521
+ .email-empty-copy {
522
+ margin-top: 0.35rem;
523
+ margin-bottom: 0.9rem;
524
+ color: var(--color-text-secondary);
525
+ line-height: 1.5;
526
+ }
527
+
528
+ .email-card {
529
+ border-radius: 0.5rem;
530
+ overflow: hidden;
531
+ }
532
+
533
+ .email-card-header {
534
+ display: flex;
535
+ justify-content: space-between;
536
+ gap: 1rem;
537
+ align-items: flex-start;
538
+ padding: var(--spacing-sm) 0;
539
+ border-bottom: 1px solid var(--color-border);
540
+ cursor: pointer;
541
+ }
542
+
543
+ .email-card-heading {
544
+ min-width: 0;
545
+ flex: 1 1 auto;
546
+ }
547
+
548
+ .email-card-title {
549
+ font-weight: 700;
550
+ font-size: 1rem;
551
+ line-height: 1.35;
552
+ overflow-wrap: anywhere;
553
+ }
554
+
555
+ .email-card-subtitle {
556
+ margin-top: 0.3rem;
557
+ color: var(--color-text-secondary);
558
+ font-size: var(--font-size-small);
559
+ line-height: 1.45;
560
+ }
561
+
562
+ .email-card-actions {
563
+ display: flex;
564
+ align-items: center;
565
+ gap: 0.45rem;
566
+ flex: 0 0 auto;
567
+ }
568
+
569
+ .email-card-chevron {
570
+ transition: transform 0.18s ease;
571
+ opacity: 0.7;
572
+ }
573
+
574
+ .email-card-chevron.is-open {
575
+ transform: rotate(180deg);
576
+ }
577
+
578
+ .email-intro-copy {
579
+ margin: 1rem 0;
580
+ padding: 0.85rem 0.95rem;
581
+ border-radius: 12px;
582
+ background: color-mix(in srgb, #3b82f6 12%, var(--color-background) 88%);
583
+ color: var(--color-text-secondary);
584
+ line-height: 1.5;
585
+ }
586
+
587
+ .email-intro-link {
588
+ display: inline-flex;
589
+ margin-top: 0.55rem;
590
+ color: var(--color-highlight);
591
+ font-weight: 600;
592
+ text-decoration: none;
593
+ }
594
+
595
+ .email-intro-link:hover {
596
+ text-decoration: underline;
597
+ }
598
+
599
+ .email-grid {
600
+ display: grid;
601
+ grid-template-columns: repeat(2, minmax(0, 1fr));
602
+ gap: 0.85rem;
603
+ }
604
+
605
+ .email-note,
606
+ .email-missing {
607
+ margin-top: 0.2rem;
608
+ margin-bottom: 0.85rem;
609
+ padding: 0.8rem 0.9rem;
610
+ border-radius: 12px;
611
+ font-size: var(--font-size-small);
612
+ line-height: 1.5;
613
+ }
614
+
615
+ .email-note {
616
+ background: color-mix(in srgb, var(--color-background) 82%, white 18%);
617
+ color: var(--color-text-secondary);
618
+ }
619
+
620
+ .email-missing {
621
+ background: color-mix(in srgb, #f59e0b 14%, var(--color-background) 86%);
622
+ color: var(--color-text-secondary);
623
+ }
624
+
625
+ .email-advanced {
626
+ margin-top: 0.95rem;
627
+ border: 1px solid color-mix(in srgb, var(--color-border) 88%, white 12%);
628
+ border-radius: 14px;
629
+ overflow: hidden;
630
+ }
631
+
632
+ .email-advanced summary {
633
+ padding: 0.9rem 1rem;
634
+ background: color-mix(in srgb, var(--color-background) 88%, white 12%);
635
+ }
636
+
637
+ .email-advanced-body {
638
+ padding: 1rem;
639
+ display: flex;
640
+ flex-direction: column;
641
+ gap: 0.2rem;
642
+ }
643
+
644
+ .email-test-panel {
645
+ margin-top: 1rem;
646
+ padding: var(--spacing-xs) 0;
647
+ display: flex;
648
+ justify-content: space-between;
649
+ align-items: center;
650
+ gap: 1rem;
651
+ }
652
+
653
+ .email-test-copy {
654
+ min-width: 0;
655
+ }
656
+
657
+ .email-test-title {
658
+ font-weight: 700;
659
+ margin-bottom: 0.25rem;
660
+ }
661
+
662
+ .email-test-description {
663
+ color: var(--color-text-secondary);
664
+ line-height: 1.5;
665
+ font-size: var(--font-size-small);
666
+ }
667
+
668
+ .email-results {
669
+ margin-top: 0.9rem;
670
+ border-radius: 14px;
671
+ border: 1px solid color-mix(in srgb, #22c55e 28%, var(--color-border) 72%);
672
+ background: color-mix(in srgb, #22c55e 8%, var(--color-background) 92%);
673
+ padding: 0.35rem 0.9rem;
674
+ }
675
+
676
+ .email-results.is-error {
677
+ border-color: color-mix(in srgb, #ef4444 28%, var(--color-border) 72%);
678
+ background: color-mix(in srgb, #ef4444 8%, var(--color-background) 92%);
679
+ }
680
+
681
+ .email-result-row {
682
+ display: flex;
683
+ align-items: flex-start;
684
+ gap: 0.8rem;
685
+ padding: 0.7rem 0;
686
+ }
687
+
688
+ .email-result-row+.email-result-row {
689
+ border-top: 1px solid color-mix(in srgb, var(--color-border) 85%, white 15%);
690
+ }
691
+
692
+ .email-result-icon {
693
+ width: 1.25rem;
694
+ font-weight: 800;
695
+ line-height: 1.3;
696
+ }
697
+
698
+ .email-result-copy {
699
+ min-width: 0;
700
+ }
701
+
702
+ .email-result-title {
703
+ font-weight: 700;
704
+ margin-bottom: 0.18rem;
705
+ }
706
+
707
+ .email-result-message {
708
+ color: var(--color-text-secondary);
709
+ line-height: 1.5;
710
+ font-size: var(--font-size-small);
711
+ overflow-wrap: anywhere;
712
+ }
713
+
714
+ .email-status-pill {
715
+ display: inline-flex;
716
+ align-items: center;
717
+ justify-content: center;
718
+ min-width: 5.25rem;
719
+ padding: 0.35rem 0.7rem;
720
+ border-radius: 999px;
721
+ font-size: 0.8rem;
722
+ font-weight: 700;
723
+ letter-spacing: 0.01em;
724
+ }
725
+
726
+ .email-status-pill.tone-success {
727
+ background: rgba(34, 197, 94, 0.14);
728
+ color: #7ee7a4;
729
+ }
730
+
731
+ .email-status-pill.tone-ready {
732
+ background: rgba(59, 130, 246, 0.16);
733
+ color: #93c5fd;
734
+ }
735
+
736
+ .email-status-pill.tone-warning {
737
+ background: rgba(245, 158, 11, 0.16);
738
+ color: #fcd34d;
739
+ }
740
+
741
+ .email-status-pill.tone-muted {
742
+ background: rgba(148, 163, 184, 0.14);
743
+ color: #cbd5e1;
744
+ }
745
+
746
+ .email-add-another {
747
+ align-self: flex-start;
748
+ }
749
+
750
+ @media (max-width: 900px) {
751
+
752
+ .email-guide-body,
753
+ .email-grid {
754
+ grid-template-columns: minmax(0, 1fr);
755
+ }
756
+ }
757
+
758
+ @media (max-width: 640px) {
759
+
760
+ .email-card-header,
761
+ .email-test-panel {
762
+ flex-direction: column;
763
+ align-items: stretch;
764
+ }
765
+
766
+ .email-card-actions {
767
+ justify-content: space-between;
768
+ width: 100%;
769
+ }
770
+
771
+ .email-status-pill {
772
+ min-width: 0;
773
+ }
774
+ }
775
+ </style>
776
</body>
777
778
</html>