Skip to content

Commit ae2d399

Browse files
authored
Hacktober apps fix (#15733)
- update dependencies in mailchimp, stripe and last email interaction apps - fix logic in mailchimp integration, now it's triggered by update of People records and allows for update Mailchimp records - fix logic in stripe integration, now properly reads data from webhook - update READMEs to make it more understandable to non-technical users
1 parent 912c668 commit ae2d399

File tree

21 files changed

+750
-317
lines changed

21 files changed

+750
-317
lines changed
Binary file not shown.

packages/twenty-apps/hacktoberfest-2025/last-email-interaction/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ twenty app sync
1717

1818
## Flow
1919
- Checks if fields are created, if not, creates them on fly
20-
- Extracts the timedate of message and calculates the last interaction status
20+
- Extracts the datetime of message and calculates the last interaction status
2121
- Fetches all users and companies connected to them and updates their Last interaction and Interaction status fields

packages/twenty-apps/hacktoberfest-2025/last-email-interaction/application.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ const config: ApplicationConfig = {
99
TWENTY_API_KEY: {
1010
universalIdentifier: 'aae3f523-4c1f-4805-b3ee-afeb676c381e',
1111
isSecret: true,
12-
value: '',
1312
description: 'Required to send requests to Twenty',
1413
},
1514
TWENTY_API_URL: {
1615
universalIdentifier: '6d19bb04-45bb-46aa-a4e5-4a2682c7b19d',
1716
isSecret: false,
18-
value: '',
1917
description: 'Optional, defaults to cloud API URL',
2018
},
2119
},

packages/twenty-apps/hacktoberfest-2025/last-email-interaction/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"packageManager": "[email protected]",
1111
"dependencies": {
1212
"axios": "^1.12.2",
13-
"twenty-sdk": "^0.0.3"
13+
"twenty-sdk": "^0.0.4"
1414
},
1515
"devDependencies": {
1616
"@types/node": "^24.7.2"

packages/twenty-apps/hacktoberfest-2025/last-email-interaction/serverlessFunctions/test/src/index.ts

Lines changed: 54 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import axios from 'axios';
2-
import { setTimeout } from 'timers/promises';
32
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
43

54
const TWENTY_API_KEY = process.env.TWENTY_API_KEY ?? '';
65
const TWENTY_URL =
76
process.env.TWENTY_API_URL !== '' && process.env.TWENTY_API_URL !== undefined
87
? `${process.env.TWENTY_API_URL}/rest`
98
: 'https://api.twenty.com/rest';
10-
const DELAY = 500;
119

1210
const create_last_interaction = (id: string) => {
1311
return {
@@ -100,6 +98,53 @@ const interactionData = (date: string, status: string) => {
10098
};
10199
};
102100

101+
const updateInteractionStatus = async (objectName: string, id: string, messageDate: string, status: string) => {
102+
const options = {
103+
method: 'PATCH',
104+
url: `${TWENTY_URL}/${objectName}/${id}`,
105+
headers: {
106+
'Content-Type': 'application/json',
107+
Authorization: `Bearer ${TWENTY_API_KEY}`,
108+
},
109+
data: {
110+
lastInteraction: messageDate,
111+
interactionStatus: status
112+
}
113+
};
114+
try {
115+
const response = await axios.request(options);
116+
if (response.status === 200) {
117+
console.log('Successfully updated company last interaction field');
118+
}
119+
} catch (error) {
120+
if (axios.isAxiosError(error)) {
121+
throw error;
122+
}
123+
throw error;
124+
}
125+
}
126+
127+
const fetchRelatedCompanyId = async (id: string) => {
128+
const options = {
129+
method: 'GET',
130+
url: `${TWENTY_URL}/people/${id}`,
131+
headers: {
132+
Authorization: `Bearer ${TWENTY_API_KEY}`,
133+
},
134+
};
135+
try {
136+
const req = await axios.request(options);
137+
if (req.status === 200 && req.data.person.companyId !== null && req.data.person.companyId !== undefined) {
138+
return req.data.person.companyId;
139+
}
140+
} catch (error) {
141+
if (axios.isAxiosError(error)) {
142+
throw error;
143+
}
144+
throw error;
145+
}
146+
}
147+
103148
export const main = async (params: {
104149
properties: Record<string, any>;
105150
recordId: string;
@@ -147,7 +192,6 @@ export const main = async (params: {
147192
if (response2.status === 201) {
148193
console.log('Successfully created company last interaction field');
149194
}
150-
await setTimeout(DELAY);
151195
}
152196
if (company_interaction_status === undefined) {
153197
const response2 = await axios.request(
@@ -156,7 +200,6 @@ export const main = async (params: {
156200
if (response2.status === 201) {
157201
console.log('Successfully created company interaction status field');
158202
}
159-
await setTimeout(DELAY);
160203
}
161204
if (person_last_interaction === undefined) {
162205
const response2 = await axios.request(
@@ -165,7 +208,6 @@ export const main = async (params: {
165208
if (response2.status === 201) {
166209
console.log('Successfully created person last interaction field');
167210
}
168-
await setTimeout(DELAY);
169211
}
170212
if (person_interaction_status === undefined) {
171213
const response2 = await axios.request(
@@ -174,11 +216,10 @@ export const main = async (params: {
174216
if (response2.status === 201) {
175217
console.log('Successfully created person interaction status field');
176218
}
177-
await setTimeout(DELAY);
178219
}
179220

180221
// Extract the timestamp of message
181-
const messageDate = properties.receivedAt;
222+
const messageDate = properties.after.receivedAt;
182223
const interactionStatus = calculateStatus(messageDate);
183224

184225
// Get the details of person and related company
@@ -190,87 +231,30 @@ export const main = async (params: {
190231
},
191232
};
192233
const messageDetails = await axios.request(messageOptions);
193-
await setTimeout(DELAY);
194-
const peopleIds = [];
234+
const peopleIds: string[] = [];
195235
for (const participant of messageDetails.data.messages
196236
.messageParticipants) {
197237
peopleIds.push(participant.personId);
198238
}
199239

200240
const companiesIds = [];
201241
for (const id of peopleIds) {
202-
const options = {
203-
method: 'GET',
204-
url: `${TWENTY_URL}/people/${id}`,
205-
headers: {
206-
Authorization: `Bearer ${TWENTY_API_KEY}`,
207-
},
208-
};
209-
try {
210-
const req = await axios.request(options);
211-
companiesIds.push(req.data.person.companyId);
212-
await setTimeout(DELAY);
213-
} catch (error) {
214-
if (axios.isAxiosError(error)) {
215-
throw error;
216-
}
217-
throw error;
218-
}
242+
companiesIds.push(await fetchRelatedCompanyId(id));
219243
}
220244
// Update the field value depending on the timestamp
221245
for (const id of peopleIds) {
222-
const peopleOptions = {
223-
method: 'PATCH',
224-
url: `${TWENTY_URL}/people/${id}`,
225-
headers: {
226-
'Content-Type': 'application/json',
227-
Authorization: `Bearer ${TWENTY_API_KEY}`,
228-
},
229-
data: interactionData(messageDate, interactionStatus),
230-
};
231-
try {
232-
const response = await axios.request(options);
233-
if (response.status === 200) {
234-
console.log('Successfully updated company last interaction field');
235-
await setTimeout(DELAY);
236-
}
237-
} catch (error) {
238-
if (axios.isAxiosError(error)) {
239-
throw error;
240-
}
241-
throw error;
242-
}
246+
await updateInteractionStatus("people", id, messageDate, interactionStatus);
243247
}
244248

245249
for (const id of companiesIds) {
246-
const companiesOptions = {
247-
method: 'PATCH',
248-
url: `${TWENTY_URL}/companies/${id}`,
249-
headers: {
250-
'Content-Type': 'application/json',
251-
Authorization: `Bearer ${TWENTY_API_KEY}`,
252-
},
253-
data: interactionData(messageDate, interactionStatus),
254-
};
255-
try {
256-
const req = await axios.request(companiesOptions);
257-
if (req.status === 200) {
258-
console.log(`Successfully updated company with ID ${id}`);
259-
await setTimeout(DELAY);
260-
}
261-
} catch (error) {
262-
if (axios.isAxiosError(error)) {
263-
throw error;
264-
}
265-
throw error;
266-
}
250+
await updateInteractionStatus("companies", id, messageDate, interactionStatus);
267251
}
268252
} catch (error) {
269253
if (axios.isAxiosError(error)) {
270-
console.error(error);
254+
console.error(error.message);
271255
return {};
272256
}
273-
console.log(error);
257+
console.error(error);
274258
return {};
275259
}
276260
};

packages/twenty-apps/hacktoberfest-2025/last-email-interaction/yarn.lock

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ __metadata:
55
version: 8
66
cacheKey: 10c0
77

8-
"Last email interaction@workspace:.":
9-
version: 0.0.0-use.local
10-
resolution: "Last email interaction@workspace:."
8+
"@types/node@npm:^24.7.2":
9+
version: 24.10.0
10+
resolution: "@types/node@npm:24.10.0"
1111
dependencies:
12-
axios: "npm:^1.12.2"
13-
languageName: unknown
14-
linkType: soft
12+
undici-types: "npm:~7.16.0"
13+
checksum: 10c0/f82ed7194e16f5590ef7afdc20c6d09068c76d50278b485ede8f0c5749683536e3064ffa8def8db76915196afb3724b854aa5723c64d6571b890b14492943b46
14+
languageName: node
15+
linkType: hard
1516

1617
"async-function@npm:^1.0.0":
1718
version: 1.0.0
@@ -217,6 +218,16 @@ __metadata:
217218
languageName: node
218219
linkType: hard
219220

221+
"last-email-interaction@workspace:.":
222+
version: 0.0.0-use.local
223+
resolution: "last-email-interaction@workspace:."
224+
dependencies:
225+
"@types/node": "npm:^24.7.2"
226+
axios: "npm:^1.12.2"
227+
twenty-sdk: "npm:^0.0.4"
228+
languageName: unknown
229+
linkType: soft
230+
220231
"math-intrinsics@npm:^1.1.0":
221232
version: 1.1.0
222233
resolution: "math-intrinsics@npm:1.1.0"
@@ -246,3 +257,17 @@ __metadata:
246257
checksum: 10c0/fe7dd8b1bdbbbea18d1459107729c3e4a2243ca870d26d34c2c1bcd3e4425b7bcc5112362df2d93cc7fb9746f6142b5e272fd1cc5c86ddf8580175186f6ad42b
247258
languageName: node
248259
linkType: hard
260+
261+
"twenty-sdk@npm:^0.0.4":
262+
version: 0.0.4
263+
resolution: "twenty-sdk@npm:0.0.4"
264+
checksum: 10c0/550f1d85bf0701396c9dd2d4c6bc55ba1b067fce13636f8540eec60ab6a4257c6d7cd86cb3f62e0974bf99467bc31270d92b17b9681a1b7a6281b7ef97224080
265+
languageName: node
266+
linkType: hard
267+
268+
"undici-types@npm:~7.16.0":
269+
version: 7.16.0
270+
resolution: "undici-types@npm:7.16.0"
271+
checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a
272+
languageName: node
273+
linkType: hard
Binary file not shown.

packages/twenty-apps/hacktoberfest-2025/mailchimp-synchronizer/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Synchronizing contacts between Twenty and Mailchimp
55
## Requirements
66
- twenty-cli `npm install -g twenty-cli`
77
- an `apiKey`. Go to `https://twenty.com/settings/api-webhooks` to generate one
8+
- Mailchimp API key - Mailchimp > avatar in top right corner > Profile > Extras > API keys
89

910
## Setup
1011
1. Add app to your workspace
@@ -13,13 +14,20 @@ twenty auth login
1314
cd mailchimp-synchronizer
1415
twenty app sync
1516
```
16-
1717
2. Go to Settings > Integrations > Mailchimp synchronizer > Settings and add required variables
1818

1919
## Flow
2020
- Check if required variables are set, if not, exit
21-
- Validate data based on set constraints
22-
- If all constraints are checked, send a request to Mailchimp with new contact
21+
- Validate data based on set constraints, if data doesn't match constraints, exit
22+
- Check if person already exists in Mailchimp:
23+
- if yes, check if UPDATE_PERSON is set to true
24+
- if UPDATE_PERSON is true, check if Twenty record is the same as Mailchimp record
25+
- if they're the same, exit
26+
- if not, update
27+
- if UPDATE_PERSON is false, exit
28+
- if person doesn't exist in Mailchimp, send a request to Mailchimp with new contact
2329

2430
## Note
25-
- SMS support is experimental and may cause errors
31+
- SMS support is experimental and may cause errors
32+
- constraints are directly responsible for sent data so if e.g. you want to have a company name in
33+
Mailchimp, you have to set IS_COMPANY_CONSTRAINT to true

0 commit comments

Comments
 (0)