@agenticmail/api 0.3.3 → 0.5.0

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.
Files changed (2) hide show
  1. package/dist/index.js +252 -0
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3080,6 +3080,257 @@ function parseTask(row) {
3080
3080
  };
3081
3081
  }
3082
3082
 
3083
+ // src/routes/sms.ts
3084
+ import { Router as Router10 } from "express";
3085
+ import {
3086
+ SmsManager,
3087
+ parseGoogleVoiceSms,
3088
+ extractVerificationCode,
3089
+ normalizePhoneNumber,
3090
+ isValidPhoneNumber
3091
+ } from "@agenticmail/core";
3092
+ function createSmsRoutes(db, accountManager, config, gatewayManager) {
3093
+ const router = Router10();
3094
+ const smsManager = new SmsManager(db);
3095
+ function getAgent(req, res) {
3096
+ const agent = req.agent;
3097
+ if (!agent) {
3098
+ res.status(401).json({ error: "Authentication required" });
3099
+ return null;
3100
+ }
3101
+ return agent;
3102
+ }
3103
+ router.get("/sms/config", (req, res) => {
3104
+ try {
3105
+ const agent = getAgent(req, res);
3106
+ if (!agent) return;
3107
+ const smsConfig = smsManager.getSmsConfig(agent.id);
3108
+ res.json({
3109
+ configured: !!smsConfig,
3110
+ sms: smsConfig ?? null
3111
+ });
3112
+ } catch (err) {
3113
+ res.status(500).json({ error: err.message });
3114
+ }
3115
+ });
3116
+ router.post("/sms/setup", (req, res) => {
3117
+ try {
3118
+ const agent = getAgent(req, res);
3119
+ if (!agent) return;
3120
+ const { phoneNumber, forwardingEmail, forwardingPassword } = req.body;
3121
+ if (!phoneNumber || typeof phoneNumber !== "string") {
3122
+ return res.status(400).json({ error: "phoneNumber is required (string)" });
3123
+ }
3124
+ if (!isValidPhoneNumber(phoneNumber)) {
3125
+ return res.status(400).json({
3126
+ error: "Invalid phone number. Provide a US number like +12125551234 or (212) 555-1234."
3127
+ });
3128
+ }
3129
+ const normalized = normalizePhoneNumber(phoneNumber);
3130
+ if (forwardingEmail && typeof forwardingEmail === "string") {
3131
+ if (!forwardingEmail.includes("@")) {
3132
+ return res.status(400).json({ error: "forwardingEmail must be a valid email address" });
3133
+ }
3134
+ }
3135
+ const fwdEmail = typeof forwardingEmail === "string" && forwardingEmail.trim() || "";
3136
+ let relayEmail = "";
3137
+ try {
3138
+ if (gatewayManager) {
3139
+ const gwStatus = gatewayManager.getStatus?.() || gatewayManager.status?.();
3140
+ if (gwStatus?.relay?.email) {
3141
+ relayEmail = gwStatus.relay.email;
3142
+ }
3143
+ }
3144
+ if (!relayEmail) {
3145
+ const gwConfig = config.gateway;
3146
+ if (gwConfig && "email" in gwConfig) relayEmail = gwConfig.email || "";
3147
+ }
3148
+ } catch {
3149
+ }
3150
+ const effectiveEmail = fwdEmail || relayEmail || agent.email || "";
3151
+ const sameAsRelay = !fwdEmail || !!relayEmail && fwdEmail.toLowerCase() === relayEmail.toLowerCase();
3152
+ const smsConfig = {
3153
+ enabled: true,
3154
+ phoneNumber: normalized,
3155
+ forwardingEmail: effectiveEmail,
3156
+ forwardingPassword: !sameAsRelay && typeof forwardingPassword === "string" && forwardingPassword.trim() ? forwardingPassword.trim() : void 0,
3157
+ sameAsRelay: !!sameAsRelay,
3158
+ provider: "google_voice",
3159
+ configuredAt: (/* @__PURE__ */ new Date()).toISOString()
3160
+ };
3161
+ smsManager.saveSmsConfig(agent.id, smsConfig);
3162
+ const nextSteps = [
3163
+ "Ensure Google Voice SMS forwarding is enabled in Settings > Messages."
3164
+ ];
3165
+ if (smsConfig.sameAsRelay) {
3166
+ nextSteps.push(`SMS will be detected automatically during email polling (same Gmail: ${effectiveEmail}).`);
3167
+ } else if (smsConfig.forwardingPassword) {
3168
+ nextSteps.push(`SMS will be polled separately from ${effectiveEmail} (separate Gmail with credentials).`);
3169
+ } else {
3170
+ nextSteps.push(`WARNING: SMS forwarding email (${effectiveEmail}) differs from relay but no password provided. SMS polling will NOT work.`);
3171
+ nextSteps.push("Provide forwardingPassword to enable SMS polling, or use the same Gmail for relay and Google Voice.");
3172
+ }
3173
+ res.json({
3174
+ success: true,
3175
+ sms: { ...smsConfig, forwardingPassword: smsConfig.forwardingPassword ? "***" : void 0 },
3176
+ nextSteps
3177
+ });
3178
+ } catch (err) {
3179
+ res.status(500).json({ error: err.message });
3180
+ }
3181
+ });
3182
+ router.post("/sms/disable", (req, res) => {
3183
+ try {
3184
+ const agent = getAgent(req, res);
3185
+ if (!agent) return;
3186
+ const existing = smsManager.getSmsConfig(agent.id);
3187
+ if (existing) {
3188
+ existing.enabled = false;
3189
+ smsManager.saveSmsConfig(agent.id, existing);
3190
+ res.json({ success: true, message: "SMS disabled" });
3191
+ } else {
3192
+ res.json({ success: true, message: "SMS was not configured" });
3193
+ }
3194
+ } catch (err) {
3195
+ res.status(500).json({ error: err.message });
3196
+ }
3197
+ });
3198
+ router.get("/sms/messages", (req, res) => {
3199
+ try {
3200
+ const agent = getAgent(req, res);
3201
+ if (!agent) return;
3202
+ const rawDir = req.query.direction;
3203
+ const direction = rawDir === "inbound" || rawDir === "outbound" ? rawDir : void 0;
3204
+ const limit = Math.min(Math.max(parseInt(req.query.limit) || 20, 1), 100);
3205
+ const offset = Math.max(parseInt(req.query.offset) || 0, 0);
3206
+ const messages = smsManager.listMessages(agent.id, { direction, limit, offset });
3207
+ res.json({ messages, count: messages.length });
3208
+ } catch (err) {
3209
+ res.status(500).json({ error: err.message });
3210
+ }
3211
+ });
3212
+ router.post("/sms/send", (req, res) => {
3213
+ try {
3214
+ const agent = getAgent(req, res);
3215
+ if (!agent) return;
3216
+ const smsConfig = smsManager.getSmsConfig(agent.id);
3217
+ if (!smsConfig?.enabled) {
3218
+ return res.status(400).json({
3219
+ error: "SMS not configured or disabled. Use sms_setup first.",
3220
+ hint: "Call agenticmail_sms_setup with your Google Voice phone number."
3221
+ });
3222
+ }
3223
+ const { to, body } = req.body;
3224
+ if (!to || typeof to !== "string") {
3225
+ return res.status(400).json({ error: '"to" (phone number) is required' });
3226
+ }
3227
+ if (!body || typeof body !== "string") {
3228
+ return res.status(400).json({ error: '"body" (message text) is required' });
3229
+ }
3230
+ if (body.length > 1600) {
3231
+ return res.status(400).json({ error: "SMS body too long (max 1600 characters, ~10 SMS segments)" });
3232
+ }
3233
+ if (!isValidPhoneNumber(to)) {
3234
+ return res.status(400).json({ error: 'Invalid "to" phone number' });
3235
+ }
3236
+ const smsRecord = smsManager.recordOutbound(agent.id, to, body, "pending");
3237
+ res.json({
3238
+ success: true,
3239
+ sms: smsRecord,
3240
+ sendInstructions: {
3241
+ method: "browser_automation",
3242
+ url: "https://voice.google.com",
3243
+ steps: [
3244
+ "Open voice.google.com in browser",
3245
+ `Click "Send a message" or navigate to Messages`,
3246
+ `Enter recipient: ${normalizePhoneNumber(to)}`,
3247
+ `Type message and send`,
3248
+ `Then call agenticmail_sms_messages to verify it was recorded`
3249
+ ]
3250
+ }
3251
+ });
3252
+ } catch (err) {
3253
+ res.status(500).json({ error: err.message });
3254
+ }
3255
+ });
3256
+ router.post("/sms/parse-email", (req, res) => {
3257
+ try {
3258
+ const agent = getAgent(req, res);
3259
+ if (!agent) return;
3260
+ const { emailBody, emailFrom } = req.body;
3261
+ if (!emailBody || typeof emailBody !== "string") {
3262
+ return res.status(400).json({ error: "emailBody is required (string)" });
3263
+ }
3264
+ const parsed = parseGoogleVoiceSms(emailBody, emailFrom || "");
3265
+ if (!parsed) {
3266
+ return res.json({
3267
+ success: false,
3268
+ parsed: null,
3269
+ verificationCode: null,
3270
+ message: "Could not parse SMS from this email. It may not be a Google Voice forwarded SMS."
3271
+ });
3272
+ }
3273
+ const msg = smsManager.recordInbound(agent.id, parsed);
3274
+ const code = extractVerificationCode(parsed.body);
3275
+ res.json({
3276
+ success: true,
3277
+ sms: msg,
3278
+ verificationCode: code
3279
+ });
3280
+ } catch (err) {
3281
+ res.status(500).json({ error: err.message });
3282
+ }
3283
+ });
3284
+ router.get("/sms/verification-code", (req, res) => {
3285
+ try {
3286
+ const agent = getAgent(req, res);
3287
+ if (!agent) return;
3288
+ const minutesBack = Math.min(Math.max(parseInt(req.query.minutes) || 10, 1), 1440);
3289
+ const result = smsManager.checkForVerificationCode(agent.id, minutesBack);
3290
+ if (result) {
3291
+ res.json({ found: true, ...result });
3292
+ } else {
3293
+ res.json({
3294
+ found: false,
3295
+ message: `No verification codes found in the last ${minutesBack} minutes.`,
3296
+ hint: "TIP: For fastest results, open https://voice.google.com/u/0/messages in the browser and read the code directly. Then use agenticmail_sms_record to save it. Email forwarding can be delayed 1-5 minutes."
3297
+ });
3298
+ }
3299
+ } catch (err) {
3300
+ res.status(500).json({ error: err.message });
3301
+ }
3302
+ });
3303
+ router.post("/sms/record", (req, res) => {
3304
+ try {
3305
+ const agent = getAgent(req, res);
3306
+ if (!agent) return;
3307
+ const { from, body } = req.body;
3308
+ if (!from || typeof from !== "string") {
3309
+ return res.status(400).json({ error: '"from" (sender phone number) is required' });
3310
+ }
3311
+ if (!body || typeof body !== "string") {
3312
+ return res.status(400).json({ error: '"body" (message text) is required' });
3313
+ }
3314
+ const normalizedFrom = normalizePhoneNumber(from) || from.replace(/[^+\d]/g, "");
3315
+ const msg = smsManager.recordInbound(agent.id, {
3316
+ from: normalizedFrom,
3317
+ body: body.trim(),
3318
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3319
+ });
3320
+ const code = extractVerificationCode(body);
3321
+ res.json({
3322
+ success: true,
3323
+ sms: msg,
3324
+ verificationCode: code,
3325
+ source: "manual_record"
3326
+ });
3327
+ } catch (err) {
3328
+ res.status(500).json({ error: err.message });
3329
+ }
3330
+ });
3331
+ return router;
3332
+ }
3333
+
3083
3334
  // src/app.ts
3084
3335
  function createApp(configOverrides) {
3085
3336
  const config = resolveConfig(configOverrides);
@@ -3129,6 +3380,7 @@ function createApp(configOverrides) {
3129
3380
  app2.use("/api/agenticmail", createGatewayRoutes(gatewayManager));
3130
3381
  app2.use("/api/agenticmail", createFeatureRoutes(db, accountManager, config, gatewayManager));
3131
3382
  app2.use("/api/agenticmail", createTaskRoutes(db, accountManager, config));
3383
+ app2.use("/api/agenticmail", createSmsRoutes(db, accountManager, config, gatewayManager));
3132
3384
  app2.use("/api/agenticmail", (_req, res) => {
3133
3385
  res.status(404).json({ error: "Not found" });
3134
3386
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/api",
3
- "version": "0.3.3",
3
+ "version": "0.5.0",
4
4
  "description": "REST API server for AgenticMail",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "prepublishOnly": "npm run build"
28
28
  },
29
29
  "dependencies": {
30
- "@agenticmail/core": "^0.3.0",
30
+ "@agenticmail/core": "^0.5.0",
31
31
  "cors": "^2.8.5",
32
32
  "dotenv": "^16.4.7",
33
33
  "express": "^4.21.0",