diff --git a/packages/backend/src/apps/signalwire/actions/index.ts b/packages/backend/src/apps/signalwire/actions/index.ts
new file mode 100644
index 00000000..d1723dc2
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/actions/index.ts
@@ -0,0 +1,3 @@
+import sendSms from './send-sms';
+
+export default [sendSms];
diff --git a/packages/backend/src/apps/signalwire/actions/send-sms/index.ts b/packages/backend/src/apps/signalwire/actions/send-sms/index.ts
new file mode 100644
index 00000000..8f91daa2
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/actions/send-sms/index.ts
@@ -0,0 +1,50 @@
+import defineAction from '../../../../helpers/define-action';
+
+export default defineAction({
+ name: 'Send an SMS',
+ key: 'sendSms',
+ description: 'Sends an SMS',
+ arguments: [
+ {
+ label: 'From Number',
+ key: 'fromNumber',
+ type: 'string' as const,
+ required: true,
+ description:
+ 'The number to send the SMS from. Include only country code. Example: 491234567890',
+ variables: true,
+ },
+ {
+ label: 'To Number',
+ key: 'toNumber',
+ type: 'string' as const,
+ required: true,
+ description:
+ 'The number to send the SMS to. Include only country code. Example: 491234567890',
+ variables: true,
+ },
+ {
+ label: 'Message',
+ key: 'message',
+ type: 'string' as const,
+ required: true,
+ description: 'The content of the message.',
+ variables: true,
+ },
+ ],
+
+ async run($) {
+ const requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages`;
+ const messageBody = $.step.parameters.message;
+
+ const fromNumber = '%2B' + ($.step.parameters.fromNumber as string).trim();
+ const toNumber = '%2B' + ($.step.parameters.toNumber as string).trim();
+
+ const response = await $.http.post(
+ 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + requestPath,
+ `Body=${messageBody}&From=${fromNumber}&To=${toNumber}`
+ );
+
+ $.setActionItem({ raw: response.data });
+ },
+});
diff --git a/packages/backend/src/apps/signalwire/assets/favicon.svg b/packages/backend/src/apps/signalwire/assets/favicon.svg
new file mode 100644
index 00000000..1dda2037
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/assets/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/backend/src/apps/signalwire/auth/index.ts b/packages/backend/src/apps/signalwire/auth/index.ts
new file mode 100644
index 00000000..009eca43
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/auth/index.ts
@@ -0,0 +1,65 @@
+import verifyCredentials from './verify-credentials';
+import isStillVerified from './is-still-verified';
+
+export default {
+ fields: [
+ {
+ key: 'accountSid',
+ label: 'Project ID',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description:
+ 'Log into your Signalwire account and find the Project ID',
+ clickToCopy: false,
+ },
+ {
+ key: 'authToken',
+ label: 'API Token',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'API Token in the respective project',
+ clickToCopy: false,
+ },
+ {
+ key: 'spaceRegion',
+ label: 'Signalwire Region',
+ type: 'dropdown' as const,
+ required: true,
+ readOnly: false,
+ value: '',
+ placeholder: null,
+ description: 'Most people should choose the default, "US"',
+ clickToCopy: false,
+ options: [
+ {
+ label: 'US',
+ value: '',
+ },
+ {
+ label: 'EU',
+ value: 'eu-',
+ },
+ ],
+ },
+ {
+ key: 'spaceName',
+ label: 'Space Name',
+ type: 'string' as const,
+ required: true,
+ readOnly: false,
+ value: null,
+ placeholder: null,
+ description: 'Name of your Signalwire space that contains the project',
+ clickToCopy: true,
+ },
+ ],
+
+ verifyCredentials,
+ isStillVerified,
+};
diff --git a/packages/backend/src/apps/signalwire/auth/is-still-verified.ts b/packages/backend/src/apps/signalwire/auth/is-still-verified.ts
new file mode 100644
index 00000000..66bb963e
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/auth/is-still-verified.ts
@@ -0,0 +1,9 @@
+import { IGlobalVariable } from '@automatisch/types';
+import verifyCredentials from './verify-credentials';
+
+const isStillVerified = async ($: IGlobalVariable) => {
+ await verifyCredentials($);
+ return true;
+};
+
+export default isStillVerified;
diff --git a/packages/backend/src/apps/signalwire/auth/verify-credentials.ts b/packages/backend/src/apps/signalwire/auth/verify-credentials.ts
new file mode 100644
index 00000000..ef75de44
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/auth/verify-credentials.ts
@@ -0,0 +1,11 @@
+import { IGlobalVariable } from '@automatisch/types';
+
+const verifyCredentials = async ($: IGlobalVariable) => {
+ await $.http.get('https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + '/api/laml/2010-04-01/Accounts');
+
+ await $.auth.set({
+ screenName: $.auth.data.accountSid,
+ });
+};
+
+export default verifyCredentials;
diff --git a/packages/backend/src/apps/signalwire/common/add-auth-header.ts b/packages/backend/src/apps/signalwire/common/add-auth-header.ts
new file mode 100644
index 00000000..f5f47f85
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/common/add-auth-header.ts
@@ -0,0 +1,20 @@
+import { TBeforeRequest } from '@automatisch/types';
+
+const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
+ if (
+ requestConfig.headers &&
+ $.auth.data?.accountSid &&
+ $.auth.data?.authToken
+ ) {
+ requestConfig.headers['Content-Type'] = 'application/x-www-form-urlencoded';
+
+ requestConfig.auth = {
+ username: $.auth.data.accountSid as string,
+ password: $.auth.data.authToken as string,
+ };
+ }
+
+ return requestConfig;
+};
+
+export default addAuthHeader;
diff --git a/packages/backend/src/apps/signalwire/index.d.ts b/packages/backend/src/apps/signalwire/index.d.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/packages/backend/src/apps/signalwire/index.ts b/packages/backend/src/apps/signalwire/index.ts
new file mode 100644
index 00000000..5df3ed0f
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/index.ts
@@ -0,0 +1,20 @@
+import defineApp from '../../helpers/define-app';
+import addAuthHeader from './common/add-auth-header';
+import auth from './auth';
+import triggers from './triggers';
+import actions from './actions';
+
+export default defineApp({
+ name: 'Signalwire',
+ key: 'signalwire',
+ iconUrl: 'https://signalwire.com/favicon.svg',
+ authDocUrl: 'https://automatisch.io/docs/apps/signalwire/connection',
+ supportsConnections: true,
+ baseUrl: 'https://signalwire.com',
+ apiBaseUrl: '',
+ primaryColor: '044cf6',
+ beforeRequest: [addAuthHeader],
+ auth,
+ triggers,
+ actions,
+});
diff --git a/packages/backend/src/apps/signalwire/triggers/index.ts b/packages/backend/src/apps/signalwire/triggers/index.ts
new file mode 100644
index 00000000..04e1504d
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/triggers/index.ts
@@ -0,0 +1,3 @@
+import receiveSms from './receive-sms';
+
+export default [receiveSms];
diff --git a/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts b/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts
new file mode 100644
index 00000000..31963130
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/triggers/receive-sms/fetch-messages.ts
@@ -0,0 +1,27 @@
+import { IGlobalVariable, IJSONObject } from '@automatisch/types';
+
+const fetchMessages = async ($: IGlobalVariable) => {
+ const toNumber = $.step.parameters.toNumber as string;
+
+ let response;
+ let requestPath = 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages?To=${toNumber}`;
+
+ do {
+ response = await $.http.get(requestPath);
+
+ response.data.messages.forEach((message: IJSONObject) => {
+ const dataItem = {
+ raw: message,
+ meta: {
+ internalId: message.date_sent as string,
+ },
+ };
+
+ $.pushTriggerItem(dataItem);
+ });
+
+ requestPath = 'https://' + $.auth.data.spaceName + '.' + $.auth.data.spaceRegion + 'signalwire.com' + response.data.next_page_uri;
+ } while (requestPath);
+};
+
+export default fetchMessages;
diff --git a/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts b/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts
new file mode 100644
index 00000000..6260d418
--- /dev/null
+++ b/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts
@@ -0,0 +1,23 @@
+import defineTrigger from '../../../../helpers/define-trigger';
+import fetchMessages from './fetch-messages';
+
+export default defineTrigger({
+ name: 'Receive SMS',
+ key: 'receiveSms',
+ pollInterval: 15,
+ description: 'Triggers when a new SMS is received.',
+ arguments: [
+ {
+ label: 'To Number',
+ key: 'toNumber',
+ type: 'string',
+ required: true,
+ description:
+ 'The number to receive the SMS on. It should be a Signalwire number in your project.',
+ },
+ ],
+
+ async run($) {
+ await fetchMessages($);
+ },
+});
diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js
index 3a16f115..f6a0589f 100644
--- a/packages/docs/pages/.vitepress/config.js
+++ b/packages/docs/pages/.vitepress/config.js
@@ -133,6 +133,16 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/scheduler/connection' },
],
},
+ {
+ text: 'Signalwire',
+ collapsible: true,
+ collapsed: true,
+ items: [
+ { text: 'Triggers', link: '/apps/signalwire/triggers' },
+ { text: 'Actions', link: '/apps/signalwire/actions' },
+ { text: 'Connection', link: '/apps/signalwire/connection' },
+ ],
+ },
{
text: 'Slack',
collapsible: true,
diff --git a/packages/docs/pages/apps/signalwire/actions.md b/packages/docs/pages/apps/signalwire/actions.md
new file mode 100644
index 00000000..bc89edd2
--- /dev/null
+++ b/packages/docs/pages/apps/signalwire/actions.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/signalwire.svg
+items:
+ - name: Send an SMS
+ desc: Sends an SMS
+---
+
+
+
+
diff --git a/packages/docs/pages/apps/signalwire/connection.md b/packages/docs/pages/apps/signalwire/connection.md
new file mode 100644
index 00000000..8100b06f
--- /dev/null
+++ b/packages/docs/pages/apps/signalwire/connection.md
@@ -0,0 +1,16 @@
+# Signalwire
+
+:::info
+This page explains the steps you need to follow to set up a Signalwire connection in Automatisch. If any of the steps are outdated, please let us know!
+:::
+
+1. Go to the Signalwire API page in your respective project (https://{space}.signalwire.com/credentials)
+2. Copy **Project ID** and paste it to the **Project ID** field on the
+ Automatisch connection creation page.
+3. Create/Copy **API Token** and paste it to the **API Token** field on the
+ Automatisch connection creation page.
+4. Select your **Region** (US for most users).
+5. Provide your **Space Name** from the URL and paste it to the **Space NAME** field on the
+ Automatisch connection creation page.
+6. Click **Submit** button on Automatisch.
+7. Now you can start using the new Signalwire connection!
diff --git a/packages/docs/pages/apps/signalwire/triggers.md b/packages/docs/pages/apps/signalwire/triggers.md
new file mode 100644
index 00000000..42083cc8
--- /dev/null
+++ b/packages/docs/pages/apps/signalwire/triggers.md
@@ -0,0 +1,12 @@
+---
+favicon: /favicons/signalwire.svg
+items:
+ - name: Receive SMS
+ desc: Triggers when a new SMS is received.
+---
+
+
+
+
diff --git a/packages/docs/pages/build-integrations/examples.md b/packages/docs/pages/build-integrations/examples.md
index 308be03f..66895181 100644
--- a/packages/docs/pages/build-integrations/examples.md
+++ b/packages/docs/pages/build-integrations/examples.md
@@ -33,6 +33,7 @@ The build integrations section is best understood when read from beginning to en
- [DeepL](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/deepl/auth/index.ts)
- [Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/auth/index.ts)
+- [Signalwire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/auth/index.ts)
- [SMTP](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/smtp/auth/index.ts)
### Without authentication
@@ -60,6 +61,7 @@ If you are developing a webhook-based trigger, you need to ensure that the webho
- [Search tweets - Twitter](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twitter/triggers/search-tweets/index.ts)
- [New issues - Github](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/github/triggers/new-issues/index.ts)
- [Receive SMS - Twilio](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/twilio/triggers/receive-sms/index.ts)
+- [Receive SMS - Signalwire](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/signalwire/triggers/receive-sms/index.ts)
- [New photos - Flickr](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/flickr/triggers/new-photos/index.ts)
### Pagination with ascending order
diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md
index 5874ca0c..6f950856 100644
--- a/packages/docs/pages/guide/available-apps.md
+++ b/packages/docs/pages/guide/available-apps.md
@@ -17,6 +17,7 @@ Following integrations are currently supported by Automatisch.
- [RSS](/apps/rss/triggers)
- [Salesforce](/apps/salesforce/triggers)
- [Scheduler](/apps/scheduler/triggers)
+- [Signalwire](/apps/signalwire/triggers)
- [Slack](/apps/slack/actions)
- [SMTP](/apps/smtp/actions)
- [Stripe](/apps/stripe/triggers)
diff --git a/packages/docs/pages/public/favicons/signalwire.svg b/packages/docs/pages/public/favicons/signalwire.svg
new file mode 100644
index 00000000..1dda2037
--- /dev/null
+++ b/packages/docs/pages/public/favicons/signalwire.svg
@@ -0,0 +1 @@
+
\ No newline at end of file