Skip to content

Commit 67c5f75

Browse files
feat: introduce tasks for Google Mail (get, list, send) + MailReceivedTrigger (#282)
* feat: added mail sub plugin * feat: implemented mail received trigger * refactor: removed unnecessary comments * fix: added notnull annotation * Update src/main/java/io/kestra/plugin/googleworkspace/mail/package-info.java Co-authored-by: François Delbrayelle <[email protected]> * refactor: changed MailReceived to MailReceivedTrigger and moved EmailMetadata to models package * feat: added fetch type in the list task * feat: mail received trigger added --------- Co-authored-by: François Delbrayelle <[email protected]>
1 parent 212c6df commit 67c5f75

File tree

20 files changed

+2144
-0
lines changed

20 files changed

+2144
-0
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ dependencies {
5757
api 'com.google.apis:google-api-services-drive:v3-rev20250819-2.0.0'
5858
api 'com.google.apis:google-api-services-sheets:v4-rev20250616-2.0.0'
5959
api 'com.google.apis:google-api-services-calendar:v3-rev20250404-2.0.0'
60+
api 'com.google.apis:google-api-services-gmail:v1-rev20250331-2.0.0'
61+
api 'javax.mail:javax.mail-api:1.6.2'
62+
api 'com.sun.mail:javax.mail:1.6.2'
63+
api 'javax.activation:activation:1.1.1'
6064

6165
// Logs
6266
compileOnly'org.slf4j:slf4j-api'
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package io.kestra.plugin.googleworkspace;
2+
3+
import io.kestra.core.models.property.Property;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
6+
import java.util.List;
7+
8+
public interface OAuthInterface {
9+
@Schema(
10+
title = "OAuth 2.0 Client ID",
11+
description = "The OAuth 2.0 client ID from Google Cloud Console"
12+
)
13+
Property<String> getClientId();
14+
15+
@Schema(
16+
title = "OAuth 2.0 Client Secret",
17+
description = "The OAuth 2.0 client secret from Google Cloud Console"
18+
)
19+
Property<String> getClientSecret();
20+
21+
@Schema(
22+
title = "OAuth 2.0 Refresh Token",
23+
description = "The OAuth 2.0 refresh token obtained through the authorization flow"
24+
)
25+
Property<String> getRefreshToken();
26+
27+
@Schema(
28+
title = "OAuth 2.0 Access Token",
29+
description = "The OAuth 2.0 access token (optional, will be generated from refresh token if not provided)"
30+
)
31+
Property<String> getAccessToken();
32+
33+
@Schema(
34+
title = "The OAuth scopes to use",
35+
description = "List of OAuth 2.0 scopes required for the operation"
36+
)
37+
Property<List<String>> getScopes();
38+
39+
@Schema(
40+
title = "The read timeout for the request (in seconds)"
41+
)
42+
Property<Integer> getReadTimeout();
43+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package io.kestra.plugin.googleworkspace.mail;
2+
3+
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
4+
import com.google.api.client.http.HttpRequest;
5+
import com.google.api.client.http.javanet.NetHttpTransport;
6+
import com.google.api.client.json.JsonFactory;
7+
import com.google.api.client.json.gson.GsonFactory;
8+
import com.google.api.services.gmail.Gmail;
9+
import com.google.api.services.gmail.GmailScopes;
10+
import com.google.auth.http.HttpCredentialsAdapter;
11+
import com.google.auth.oauth2.AccessToken;
12+
import com.google.auth.oauth2.GoogleCredentials;
13+
import com.google.auth.oauth2.UserCredentials;
14+
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
15+
import io.kestra.core.models.property.Property;
16+
import io.kestra.core.models.tasks.Task;
17+
import io.kestra.core.runners.RunContext;
18+
import io.kestra.plugin.googleworkspace.OAuthInterface;
19+
import io.swagger.v3.oas.annotations.media.Schema;
20+
import jakarta.validation.constraints.NotNull;
21+
import lombok.*;
22+
import lombok.experimental.SuperBuilder;
23+
24+
import java.io.IOException;
25+
import java.security.GeneralSecurityException;
26+
import java.util.List;
27+
28+
@SuperBuilder
29+
@ToString
30+
@EqualsAndHashCode
31+
@Getter
32+
@NoArgsConstructor
33+
public abstract class AbstractMail extends Task implements OAuthInterface {
34+
protected static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
35+
36+
@Schema(
37+
title = "OAuth 2.0 Client ID",
38+
description = "The OAuth 2.0 client ID from Google Cloud Console"
39+
)
40+
@NotNull
41+
protected Property<String> clientId;
42+
43+
@Schema(
44+
title = "OAuth 2.0 Client Secret",
45+
description = "The OAuth 2.0 client secret from Google Cloud Console"
46+
)
47+
@NotNull
48+
protected Property<String> clientSecret;
49+
50+
@Schema(
51+
title = "OAuth 2.0 Refresh Token",
52+
description = "The OAuth 2.0 refresh token obtained through the authorization flow"
53+
)
54+
@NotNull
55+
protected Property<String> refreshToken;
56+
57+
@Schema(
58+
title = "OAuth 2.0 Access Token",
59+
description = "The OAuth 2.0 access token (optional, will be generated from refresh token if not provided)"
60+
)
61+
protected Property<String> accessToken;
62+
63+
@Builder.Default
64+
protected Property<List<String>> scopes = Property.ofValue(List.of(
65+
GmailScopes.GMAIL_MODIFY,
66+
GmailScopes.GMAIL_READONLY,
67+
GmailScopes.GMAIL_SEND
68+
));
69+
70+
@Builder.Default
71+
protected Property<Integer> readTimeout = Property.ofValue(120);
72+
73+
protected Gmail connection(RunContext runContext) throws IllegalVariableEvaluationException, IOException, GeneralSecurityException {
74+
HttpCredentialsAdapter credentials = this.oauthCredentials(runContext);
75+
76+
return new Gmail.Builder(this.netHttpTransport(), JSON_FACTORY, credentials)
77+
.setApplicationName("Kestra")
78+
.build();
79+
}
80+
81+
protected HttpCredentialsAdapter oauthCredentials(RunContext runContext) throws IllegalVariableEvaluationException, IOException {
82+
// Get OAuth parameters
83+
String rClientId = runContext.render(this.clientId).as(String.class)
84+
.orElseThrow(() -> new IllegalArgumentException("clientId is required for OAuth authentication"));
85+
String rClientSecret = runContext.render(this.clientSecret).as(String.class)
86+
.orElseThrow(() -> new IllegalArgumentException("clientSecret is required for OAuth authentication"));
87+
String rRefreshToken = runContext.render(this.refreshToken).as(String.class)
88+
.orElseThrow(() -> new IllegalArgumentException("refreshToken is required for OAuth authentication"));
89+
90+
// Optional access token
91+
String rAccessToken = runContext.render(this.accessToken).as(String.class).orElse(null);
92+
93+
runContext.logger().debug("Setting up OAuth credentials for Gmail API");
94+
95+
// Create UserCredentials for OAuth
96+
UserCredentials.Builder credentialsBuilder = UserCredentials.newBuilder()
97+
.setClientId(rClientId)
98+
.setClientSecret(rClientSecret)
99+
.setRefreshToken(rRefreshToken);
100+
101+
if (rAccessToken != null && !rAccessToken.trim().isEmpty()) {
102+
credentialsBuilder.setAccessToken(new AccessToken(rAccessToken, null));
103+
runContext.logger().debug("Using provided access token for authentication");
104+
}
105+
106+
GoogleCredentials credentials = credentialsBuilder.build();
107+
108+
// Apply scopes if specified
109+
var rScopes = runContext.render(this.scopes).asList(String.class);
110+
if (rScopes != null && !rScopes.isEmpty()) {
111+
credentials = credentials.createScoped(rScopes);
112+
runContext.logger().debug("Applied {} OAuth scopes", rScopes.size());
113+
}
114+
115+
var rTimeout = runContext.render(this.readTimeout).as(Integer.class).orElse(120);
116+
return new HttpCredentialsAdapter(credentials) {
117+
@Override
118+
public void initialize(HttpRequest request) throws IOException {
119+
super.initialize(request);
120+
request.setReadTimeout(rTimeout * 1000);
121+
}
122+
};
123+
}
124+
125+
protected NetHttpTransport netHttpTransport() throws GeneralSecurityException, IOException {
126+
return GoogleNetHttpTransport.newTrustedTransport();
127+
}
128+
}

0 commit comments

Comments
 (0)