@agenticmail/enterprise 0.5.127 → 0.5.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-3UJJAD4H.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-RO537U6H.js";
6
+ import "./chunk-DRXMYYKN.js";
7
+ import "./chunk-67KZYSLU.js";
8
+ import "./chunk-JLSQOQ5L.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-EUMQIDYD.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-RO537U6H.js";
6
+ import "./chunk-DRXMYYKN.js";
7
+ import "./chunk-67KZYSLU.js";
8
+ import "./chunk-JLSQOQ5L.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-WYFJXCSH.js";
10
+ import "./chunk-MHIFVS5L.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-M6GX25GK.js";
10
+ import "./chunk-MHIFVS5L.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.127",
3
+ "version": "0.5.129",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -470,6 +470,7 @@ export function createAdminRoutes(db: DatabaseAdapter) {
470
470
  { field: 'cfApiToken', type: 'string', maxLength: 500 },
471
471
  { field: 'cfAccountId', type: 'string', maxLength: 100 },
472
472
  { field: 'plan', type: 'string', maxLength: 32 },
473
+ { field: 'signatureTemplate', type: 'string', maxLength: 10000 },
473
474
  ]);
474
475
 
475
476
  const settings = await db.updateSettings(body);
@@ -355,9 +355,17 @@ export function createGmailTools(config: GoogleToolsConfig, _options?: ToolCreat
355
355
  const original = await gmail(token, `/messages/${params.messageId}`, { query: { format: 'metadata', metadataHeaders: 'From,To,Cc,Subject,Message-ID,References' } });
356
356
  const oh = extractHeaders(original.payload?.headers || [], ['From', 'To', 'Cc', 'Subject', 'Message-ID', 'References']);
357
357
 
358
+ // Extract clean email from From header (handles "Name <email>" format)
359
+ const extractEmail = (h: string) => {
360
+ const m = h.match(/<([^>]+)>/);
361
+ return m ? m[1] : h.trim();
362
+ };
358
363
  const to = params.replyAll === 'true'
359
- ? [oh.From, ...(oh.To || '').split(','), ...(oh.Cc || '').split(',')].filter(e => e && !e.includes(email || '___')).join(',')
360
- : oh.From;
364
+ ? [oh.From, ...(oh.To || '').split(','), ...(oh.Cc || '').split(',')]
365
+ .map(e => extractEmail(e))
366
+ .filter(e => e && !e.includes(email || '___'))
367
+ .join(',')
368
+ : extractEmail(oh.From);
361
369
 
362
370
  const subject = oh.Subject?.startsWith('Re:') ? oh.Subject : `Re: ${oh.Subject || ''}`;
363
371
  const references = [oh.References, oh['Message-ID']].filter(Boolean).join(' ');
@@ -692,5 +700,63 @@ export function createGmailTools(config: GoogleToolsConfig, _options?: ToolCreat
692
700
  } catch (e: any) { return errorResult(e.message); }
693
701
  },
694
702
  },
703
+
704
+ // ─── Signature Management ────────────────────────────
705
+ {
706
+ name: 'gmail_get_signature',
707
+ description: 'Get the current email signature for the primary send-as alias.',
708
+ category: 'utility' as const,
709
+ parameters: {
710
+ type: 'object' as const,
711
+ properties: {},
712
+ required: [],
713
+ },
714
+ async execute(_id: string) {
715
+ try {
716
+ const token = await getToken();
717
+ // Get send-as settings for the primary email
718
+ const sendAs = await gmail(token, '/settings/sendAs');
719
+ const primary = sendAs.sendAs?.find((s: any) => s.isPrimary) || sendAs.sendAs?.[0];
720
+ if (!primary) return errorResult('No primary send-as alias found');
721
+ return jsonResult({
722
+ email: primary.sendAsEmail,
723
+ displayName: primary.displayName,
724
+ signature: primary.signature || '(no signature set)',
725
+ isDefault: primary.isDefault,
726
+ });
727
+ } catch (e: any) { return errorResult(e.message); }
728
+ },
729
+ },
730
+ {
731
+ name: 'gmail_set_signature',
732
+ description: 'Set or update the email signature for the primary send-as alias. Accepts HTML for rich signatures with images, links, and formatting.',
733
+ category: 'utility' as const,
734
+ parameters: {
735
+ type: 'object' as const,
736
+ properties: {
737
+ signature: { type: 'string', description: 'HTML signature content. Use HTML tags for formatting: <b>bold</b>, <a href="...">links</a>, <img src="..."> for logos, <br> for line breaks, <table> for layout.' },
738
+ displayName: { type: 'string', description: 'Display name for the send-as alias (optional)' },
739
+ },
740
+ required: ['signature'],
741
+ },
742
+ async execute(_id: string, params: any) {
743
+ try {
744
+ const token = await getToken();
745
+ // Get primary send-as email
746
+ const sendAs = await gmail(token, '/settings/sendAs');
747
+ const primary = sendAs.sendAs?.find((s: any) => s.isPrimary) || sendAs.sendAs?.[0];
748
+ if (!primary) return errorResult('No primary send-as alias found');
749
+
750
+ const update: any = { signature: params.signature };
751
+ if (params.displayName) update.displayName = params.displayName;
752
+
753
+ const result = await gmail(token, `/settings/sendAs/${encodeURIComponent(primary.sendAsEmail)}`, {
754
+ method: 'PATCH',
755
+ body: update,
756
+ });
757
+ return jsonResult({ updated: true, email: primary.sendAsEmail, signatureLength: params.signature.length });
758
+ } catch (e: any) { return errorResult(e.message); }
759
+ },
760
+ },
695
761
  ];
696
762
  }
package/src/cli-agent.ts CHANGED
@@ -418,7 +418,45 @@ Available tools: gmail_send (to, subject, body) or agenticmail_send (to, subject
418
418
  console.error(`[onboarding] Error: ${err.message}`);
419
419
  }
420
420
 
421
- // 12. Start email inbox polling loop
421
+ // 12. Auto-setup Gmail signature from org template
422
+ try {
423
+ const orgSettings = await db.getSettings();
424
+ const sigTemplate = (orgSettings as any)?.signatureTemplate;
425
+ if (sigTemplate && currentAccessToken) {
426
+ const agentName = config.displayName || config.name;
427
+ const role = config.identity?.role || 'AI Agent';
428
+ const agentEmailAddr = config.email?.address || emailConfig?.email || '';
429
+ const companyName = orgSettings?.name || '';
430
+ const logoUrl = orgSettings?.logoUrl || '';
431
+
432
+ const signature = sigTemplate
433
+ .replace(/\{\{name\}\}/g, agentName)
434
+ .replace(/\{\{role\}\}/g, role)
435
+ .replace(/\{\{email\}\}/g, agentEmailAddr)
436
+ .replace(/\{\{company\}\}/g, companyName)
437
+ .replace(/\{\{logo\}\}/g, logoUrl)
438
+ .replace(/\{\{phone\}\}/g, '');
439
+
440
+ // Set Gmail signature via API
441
+ const sendAsRes = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs', {
442
+ headers: { Authorization: `Bearer ${currentAccessToken}` },
443
+ });
444
+ const sendAs = await sendAsRes.json() as any;
445
+ const primary = sendAs.sendAs?.find((s: any) => s.isPrimary) || sendAs.sendAs?.[0];
446
+ if (primary) {
447
+ await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/settings/sendAs/${encodeURIComponent(primary.sendAsEmail)}`, {
448
+ method: 'PATCH',
449
+ headers: { Authorization: `Bearer ${currentAccessToken}`, 'Content-Type': 'application/json' },
450
+ body: JSON.stringify({ signature }),
451
+ });
452
+ console.log(`[signature] ✅ Gmail signature set for ${primary.sendAsEmail}`);
453
+ }
454
+ }
455
+ } catch (sigErr: any) {
456
+ console.log(`[signature] Skipped: ${sigErr.message}`);
457
+ }
458
+
459
+ // 13. Start email inbox polling loop
422
460
  startEmailPolling(AGENT_ID, config, lifecycle, runtime, engineDb, memoryManager);
423
461
  }, 3000);
424
462
  }
@@ -509,12 +547,11 @@ async function startEmailPolling(
509
547
  try {
510
548
  const messages = await emailProvider.listMessages('INBOX', { limit: 20 });
511
549
  const newMessages = messages.filter(m => !processedIds.has(m.uid));
512
- const unread = newMessages.filter(m => !m.read);
513
550
  if (newMessages.length > 0) {
514
- console.log(`[email-poll] Found ${newMessages.length} new messages, ${unread.length} unread`);
551
+ console.log(`[email-poll] Found ${newMessages.length} new messages`);
515
552
  }
516
553
 
517
- for (const envelope of unread) {
554
+ for (const envelope of newMessages) {
518
555
  processedIds.add(envelope.uid);
519
556
 
520
557
  // Skip emails from ourselves
@@ -606,6 +643,12 @@ IMPORTANT: Use gmail_reply, NOT gmail_send. gmail_send creates a new email threa
606
643
  Be helpful, professional, and match the tone of the sender.
607
644
  Keep responses concise but thorough. Sign off with your name: ${agentName}
608
645
 
646
+ FORMATTING RULES:
647
+ - NEVER use "--" or "---" or em dashes (—) in your emails
648
+ - NEVER use markdown formatting (no **, no ##, no bullet points with -)
649
+ - Write natural, flowing prose like a real human email
650
+ - Keep it warm and conversational, not robotic or formatted
651
+
609
652
  DO NOT just generate text — you MUST call gmail_reply to actually send the reply.`;
610
653
 
611
654
  const session = await runtime.spawnSession({
@@ -256,6 +256,61 @@ export function SettingsPage() {
256
256
  h('button', { className: 'btn btn-primary', onClick: () => apiCall('/settings', { method: 'PATCH', body: JSON.stringify({ name: settings.name, domain: settings.domain, subdomain: settings.subdomain, logoUrl: settings.logoUrl, primaryColor: settings.primaryColor, plan: settings.plan }) }).then(d => { setSettings(d); toast('Settings saved', 'success'); }).catch(e => toast(e.message, 'error')) }, 'Save Changes')
257
257
  )
258
258
  ),
259
+
260
+ // ─── Email Signature Template ─────────────────────
261
+ h('div', { className: 'card' },
262
+ h('div', { className: 'card-header' },
263
+ h('h3', null, 'Email Signature Template'),
264
+ h('span', { style: { fontSize: 12, color: 'var(--text-muted)', marginLeft: 8 } }, 'Applied to all agents')
265
+ ),
266
+ h('div', { className: 'card-body' },
267
+ h('p', { style: { fontSize: 13, color: 'var(--text-muted)', marginBottom: 16 } }, 'Define an HTML signature template that agents will use in their Gmail accounts. Use {{name}}, {{role}}, {{email}}, {{phone}}, {{company}} as placeholders.'),
268
+ h('div', { className: 'form-group' },
269
+ h('label', { className: 'form-label' }, 'Signature HTML Template'),
270
+ h('textarea', {
271
+ className: 'input',
272
+ value: settings.signatureTemplate || '',
273
+ onChange: function(e) { setSettings(function(s) { return Object.assign({}, s, { signatureTemplate: e.target.value }); }); },
274
+ placeholder: '<table cellpadding="0" cellspacing="0" style="font-family: Arial, sans-serif; font-size: 13px; color: #333;">\n <tr>\n <td style="padding-right: 15px; border-right: 2px solid #6366f1;">\n <img src="{{logo}}" width="60" alt="{{company}}">\n </td>\n <td style="padding-left: 15px;">\n <b style="font-size: 14px;">{{name}}</b><br>\n <span style="color: #6366f1;">{{role}}</span><br>\n <span style="color: #888;">{{email}}</span><br>\n <span style="color: #888;">{{company}}</span>\n </td>\n </tr>\n</table>',
275
+ rows: 12,
276
+ style: { fontFamily: 'var(--font-mono)', fontSize: 12, resize: 'vertical' }
277
+ })
278
+ ),
279
+ h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 } },
280
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{name}}'),
281
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{role}}'),
282
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{email}}'),
283
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{phone}}'),
284
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{company}}'),
285
+ h('span', { className: 'badge', style: { fontSize: 11 } }, '{{logo}}')
286
+ ),
287
+ settings.signatureTemplate && h('div', { style: { marginBottom: 16 } },
288
+ h('label', { className: 'form-label' }, 'Preview'),
289
+ h('div', {
290
+ style: { background: 'white', padding: 16, borderRadius: 'var(--radius)', border: '1px solid var(--border)', color: '#333' },
291
+ dangerouslySetInnerHTML: {
292
+ __html: (settings.signatureTemplate || '')
293
+ .replace(/\{\{name\}\}/g, 'Jane Smith')
294
+ .replace(/\{\{role\}\}/g, 'Customer Support Lead')
295
+ .replace(/\{\{email\}\}/g, 'jane@company.com')
296
+ .replace(/\{\{phone\}\}/g, '+1 (555) 123-4567')
297
+ .replace(/\{\{company\}\}/g, settings.name || 'Your Company')
298
+ .replace(/\{\{logo\}\}/g, settings.logoUrl || 'https://placehold.co/60x60?text=Logo')
299
+ }
300
+ })
301
+ ),
302
+ h('button', {
303
+ className: 'btn btn-primary',
304
+ onClick: function() {
305
+ apiCall('/settings', {
306
+ method: 'PATCH',
307
+ body: JSON.stringify({ signatureTemplate: settings.signatureTemplate })
308
+ }).then(function() { toast('Signature template saved', 'success'); }).catch(function(e) { toast(e.message, 'error'); });
309
+ }
310
+ }, 'Save Signature Template')
311
+ )
312
+ ),
313
+
259
314
  h('div', { className: 'card' },
260
315
  h('div', { className: 'card-header' },
261
316
  h('h3', null, 'Organization Email'),
@@ -150,6 +150,7 @@ export class PostgresAdapter extends DatabaseAdapter {
150
150
  domainStatus: 'domain_status',
151
151
  cfApiToken: 'cf_api_token',
152
152
  cfAccountId: 'cf_account_id',
153
+ signatureTemplate: 'signature_template',
153
154
  };
154
155
  for (const [key, col] of Object.entries(map)) {
155
156
  if ((updates as any)[key] !== undefined) {
@@ -562,6 +563,7 @@ export class PostgresAdapter extends DatabaseAdapter {
562
563
  cfApiToken: r.cf_api_token || undefined,
563
564
  cfAccountId: r.cf_account_id || undefined,
564
565
  orgEmailConfig: r.org_email_config ? (typeof r.org_email_config === 'string' ? JSON.parse(r.org_email_config) : r.org_email_config) : undefined,
565
- };
566
+ signatureTemplate: r.signature_template || undefined,
567
+ } as any;
566
568
  }
567
569
  }
@@ -1297,6 +1297,14 @@ CREATE INDEX IF NOT EXISTS idx_kc_entries_base ON knowledge_contribution_entries
1297
1297
  mysql: `SELECT 1;`,
1298
1298
  nosql: async () => {},
1299
1299
  },
1300
+ {
1301
+ version: 19,
1302
+ name: 'signature_template',
1303
+ sql: `ALTER TABLE company_settings ADD COLUMN signature_template TEXT;`,
1304
+ postgres: `ALTER TABLE company_settings ADD COLUMN IF NOT EXISTS signature_template TEXT;`,
1305
+ mysql: `ALTER TABLE company_settings ADD COLUMN signature_template TEXT;`,
1306
+ nosql: async () => {},
1307
+ },
1300
1308
  ];
1301
1309
 
1302
1310
  // ─── Dynamic Table Definitions ─────────────────────────