mailtrap 2.3.0 → 2.4.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.
@@ -9,57 +9,193 @@ module Mailtrap
9
9
  SENDING_API_HOST = 'send.api.mailtrap.io'
10
10
  BULK_SENDING_API_HOST = 'bulk.api.mailtrap.io'
11
11
  SANDBOX_API_HOST = 'sandbox.api.mailtrap.io'
12
+ GENERAL_API_HOST = 'mailtrap.io'
12
13
  API_PORT = 443
13
14
 
14
- attr_reader :api_key, :api_host, :api_port, :bulk, :sandbox, :inbox_id
15
+ attr_reader :api_key, :api_host, :general_api_host, :api_port, :bulk, :sandbox, :inbox_id
15
16
 
16
17
  # Initializes a new Mailtrap::Client instance.
17
18
  #
18
19
  # @param [String] api_key The Mailtrap API key to use for sending. Required.
19
- # If not set, is taken from the MAILTRAP_API_KEY environment variable.
20
- # @param [String, nil] api_host The Mailtrap API hostname. If not set, is chosen internally.
20
+ # If not set, it is taken from the MAILTRAP_API_KEY environment variable.
21
+ # @param [String] api_host The Mailtrap API hostname. If not set, it is chosen internally.
22
+ # @param [String] general_api_host The Mailtrap general API hostname for non-sending operations.
21
23
  # @param [Integer] api_port The Mailtrap API port. Default: 443.
22
24
  # @param [Boolean] bulk Whether to use the Mailtrap bulk sending API. Default: false.
23
- # If enabled, is incompatible with `sandbox: true`.
25
+ # If enabled, it is incompatible with `sandbox: true`.
24
26
  # @param [Boolean] sandbox Whether to use the Mailtrap sandbox API. Default: false.
25
- # If enabled, is incompatible with `bulk: true`.
27
+ # If enabled, it is incompatible with `bulk: true`.
26
28
  # @param [Integer] inbox_id The sandbox inbox ID to send to. Required if sandbox API is used.
29
+ # @raise [ArgumentError] If api_key or api_port is nil, or if sandbox is true but inbox_id is nil,
30
+ # or if incompatible options are provided.
27
31
  def initialize( # rubocop:disable Metrics/ParameterLists
28
32
  api_key: ENV.fetch('MAILTRAP_API_KEY'),
29
33
  api_host: nil,
34
+ general_api_host: GENERAL_API_HOST,
30
35
  api_port: API_PORT,
31
36
  bulk: false,
32
37
  sandbox: false,
33
38
  inbox_id: nil
34
39
  )
35
- raise ArgumentError, 'api_key is required' if api_key.nil?
36
- raise ArgumentError, 'api_port is required' if api_port.nil?
40
+ validate_args!(api_key, api_port, bulk, sandbox, inbox_id)
37
41
 
38
42
  api_host ||= select_api_host(bulk:, sandbox:)
39
- raise ArgumentError, 'inbox_id is required for sandbox API' if sandbox && inbox_id.nil?
40
43
 
41
44
  @api_key = api_key
42
45
  @api_host = api_host
46
+ @general_api_host = general_api_host
43
47
  @api_port = api_port
44
48
  @bulk = bulk
45
49
  @sandbox = sandbox
46
50
  @inbox_id = inbox_id
51
+ @http_clients = {}
52
+ end
53
+
54
+ # Sends a batch of emails.
55
+ # @example Batch sending with template
56
+ # batch_base = Mailtrap::Mail.batch_base_from_template(
57
+ # from: { email: 'mailtrap@demomailtrap.co', name: 'Mailtrap Test' },
58
+ # reply_to: { email: 'support@example.com', name: 'Mailtrap Reply-To' },
59
+ # template_uuid: '339c8ab0-e73c-4269-984e-0d2446aacf2c'
60
+ # )
61
+ #
62
+ # client.send_batch(
63
+ # batch_base, [
64
+ # Mailtrap::Mail.from_template(
65
+ # to: [
66
+ # { email: 'john.doe@email.com', name: 'John Doe' }
67
+ # ],
68
+ # template_variables: {
69
+ # user_name: 'John Doe'
70
+ # }
71
+ # ),
72
+ # Mailtrap::Mail::Base.new(
73
+ # to: [
74
+ # { email: 'jane.doe@email.com', name: 'Jane Doe' }
75
+ # ],
76
+ # template_variables: {
77
+ # user_name: 'Jane Doe'
78
+ # }
79
+ # ),
80
+ # {
81
+ # to: [
82
+ # { email: 'david.doe@email.com', name: 'David Doe' }
83
+ # ],
84
+ # template_variables: {
85
+ # user_name: 'David Doe'
86
+ # }
87
+ # }
88
+ # ]
89
+ # )
90
+ #
91
+ # @example Passing the request parameters directly
92
+ # client.send_batch(
93
+ # {
94
+ # from: { email: 'mailtrap@demomailtrap.co', name: 'Mailtrap Test' },
95
+ # reply_to: { email: 'support@example.com', name: 'Mailtrap Reply-To' },
96
+ # template_uuid: '339c8ab0-e73c-4269-984e-0d2446aacf2c'
97
+ # }, [
98
+ # {
99
+ # to: [
100
+ # { email: 'john.doe@email.com', name: 'John Doe' }
101
+ # ],
102
+ # template_variables: {
103
+ # user_name: 'John Doe'
104
+ # }
105
+ # },
106
+ # {
107
+ # to: [
108
+ # { email: 'jane.doe@email.com', name: 'Jane Doe' }
109
+ # ],
110
+ # template_variables: {
111
+ # user_name: 'Jane Doe'
112
+ # }
113
+ # }
114
+ # ]
115
+ # )
116
+ # @param base [#to_json] The base email configuration for the batch.
117
+ # @param requests [Array<#to_json>] Array of individual email requests.
118
+ # @return [Hash] The JSON response from the API.
119
+ # @!macro api_errors
120
+ # @raise [Mailtrap::MailSizeError] If the message is too large.
121
+ def send_batch(base, requests)
122
+ perform_request(:post, api_host, batch_request_path, {
123
+ base:,
124
+ requests:
125
+ })
47
126
  end
48
127
 
128
+ # Sends an email
129
+ # @example
130
+ # mail = Mailtrap::Mail.from_template(
131
+ # from: { email: 'mailtrap@example.com', name: 'Mailtrap Test' },
132
+ # to: [
133
+ # { email: 'your@email.com' }
134
+ # ],
135
+ # template_uuid: '2f45b0aa-bbed-432f-95e4-e145e1965ba2',
136
+ # template_variables: {
137
+ # 'user_name' => 'John Doe'
138
+ # }
139
+ # )
140
+ # client.send(mail)
141
+ # @example
142
+ # client.send(
143
+ # from: { email: 'mailtrap@example.com', name: 'Mailtrap Test' },
144
+ # to: [
145
+ # { email: 'your@email.com' }
146
+ # ],
147
+ # subject: 'You are awesome!',
148
+ # text: 'Congrats for sending test email with Mailtrap!'
149
+ # )
150
+ # @param mail [#to_json] The email to send
151
+ # @return [Hash] The JSON response
152
+ # @!macro api_errors
153
+ # @raise [Mailtrap::MailSizeError] If the message is too large
49
154
  def send(mail)
50
- raise ArgumentError, 'should be Mailtrap::Mail::Base object' unless mail.is_a? Mail::Base
155
+ perform_request(:post, api_host, send_path, mail)
156
+ end
51
157
 
52
- request = post_request(request_url, mail.to_json)
53
- response = http_client.request(request)
158
+ # Performs a GET request to the specified path
159
+ # @param path [String] The request path
160
+ # @return [Hash, nil] The JSON response
161
+ # @!macro api_errors
162
+ def get(path)
163
+ perform_request(:get, general_api_host, path)
164
+ end
54
165
 
55
- handle_response(response)
166
+ # Performs a POST request to the specified path
167
+ # @param path [String] The request path
168
+ # @param body [Hash] The request body
169
+ # @return [Hash, nil] The JSON response
170
+ # @!macro api_errors
171
+ def post(path, body = nil)
172
+ perform_request(:post, general_api_host, path, body)
173
+ end
174
+
175
+ # Performs a PATCH request to the specified path
176
+ # @param path [String] The request path
177
+ # @param body [Hash] The request body
178
+ # @return [Hash, nil] The JSON response
179
+ # @!macro api_errors
180
+ def patch(path, body = nil)
181
+ perform_request(:patch, general_api_host, path, body)
182
+ end
183
+
184
+ # Performs a DELETE request to the specified path
185
+ # @param path [String] The request path
186
+ # @return [Hash, nil] The JSON response
187
+ # @!macro api_errors
188
+ def delete(path)
189
+ perform_request(:delete, general_api_host, path)
56
190
  end
57
191
 
58
192
  private
59
193
 
60
- def select_api_host(bulk:, sandbox:)
61
- raise ArgumentError, 'bulk mode is not applicable for sandbox API' if bulk && sandbox
194
+ def http_client_for(host)
195
+ @http_clients[host] ||= Net::HTTP.new(host, api_port).tap { |client| client.use_ssl = true }
196
+ end
62
197
 
198
+ def select_api_host(bulk:, sandbox:)
63
199
  if sandbox
64
200
  SANDBOX_API_HOST
65
201
  elsif bulk
@@ -69,17 +205,36 @@ module Mailtrap
69
205
  end
70
206
  end
71
207
 
72
- def request_url
73
- "/api/send#{sandbox ? "/#{inbox_id}" : ""}"
208
+ def send_path
209
+ "/api/send#{"/#{inbox_id}" if sandbox}"
210
+ end
211
+
212
+ def batch_request_path
213
+ "/api/batch#{"/#{inbox_id}" if sandbox}"
74
214
  end
75
215
 
76
- def http_client
77
- @http_client ||= Net::HTTP.new(api_host, api_port).tap { |client| client.use_ssl = true }
216
+ def perform_request(method, host, path, body = nil)
217
+ http_client = http_client_for(host)
218
+ request = setup_request(method, path, body)
219
+ response = http_client.request(request)
220
+ handle_response(response)
78
221
  end
79
222
 
80
- def post_request(path, body)
81
- request = Net::HTTP::Post.new(path)
82
- request.body = body
223
+ def setup_request(method, path, body = nil)
224
+ request = case method
225
+ when :get
226
+ Net::HTTP::Get.new(path)
227
+ when :post
228
+ Net::HTTP::Post.new(path)
229
+ when :patch
230
+ Net::HTTP::Patch.new(path)
231
+ when :delete
232
+ Net::HTTP::Delete.new(path)
233
+ else
234
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
235
+ end
236
+
237
+ request.body = body.to_json if body
83
238
  request['Authorization'] = "Bearer #{api_key}"
84
239
  request['Content-Type'] = 'application/json'
85
240
  request['User-Agent'] = 'mailtrap-ruby (https://github.com/railsware/mailtrap-ruby)'
@@ -87,22 +242,26 @@ module Mailtrap
87
242
  request
88
243
  end
89
244
 
90
- def handle_response(response) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
245
+ def handle_response(response) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
91
246
  case response
92
- when Net::HTTPOK
247
+ when Net::HTTPOK, Net::HTTPCreated
93
248
  json_response(response.body)
249
+ when Net::HTTPNoContent
250
+ nil
94
251
  when Net::HTTPBadRequest
95
- raise Mailtrap::Error, json_response(response.body)[:errors]
252
+ raise Mailtrap::Error, ['bad request'] if response.body.empty?
253
+
254
+ raise Mailtrap::Error, response_errors(response.body)
96
255
  when Net::HTTPUnauthorized
97
- raise Mailtrap::AuthorizationError, json_response(response.body)[:errors]
256
+ raise Mailtrap::AuthorizationError, response_errors(response.body)
98
257
  when Net::HTTPForbidden
99
- raise Mailtrap::RejectionError, json_response(response.body)[:errors]
258
+ raise Mailtrap::RejectionError, response_errors(response.body)
100
259
  when Net::HTTPPayloadTooLarge
101
260
  raise Mailtrap::MailSizeError, ['message too large']
102
261
  when Net::HTTPTooManyRequests
103
262
  raise Mailtrap::RateLimitError, ['too many requests']
104
263
  when Net::HTTPClientError
105
- raise Mailtrap::Error, ['client error']
264
+ raise Mailtrap::Error, ["client error '#{response.body}'"]
106
265
  when Net::HTTPServerError
107
266
  raise Mailtrap::Error, ['server error']
108
267
  else
@@ -110,8 +269,20 @@ module Mailtrap
110
269
  end
111
270
  end
112
271
 
272
+ def response_errors(body)
273
+ parsed_body = json_response(body)
274
+ Array(parsed_body[:errors] || parsed_body[:error])
275
+ end
276
+
113
277
  def json_response(body)
114
278
  JSON.parse(body, symbolize_names: true)
115
279
  end
280
+
281
+ def validate_args!(api_key, api_port, bulk, sandbox, inbox_id)
282
+ raise ArgumentError, 'api_key is required' if api_key.nil?
283
+ raise ArgumentError, 'api_port is required' if api_port.nil?
284
+ raise ArgumentError, 'bulk stream is not applicable for sandbox API' if bulk && sandbox
285
+ raise ArgumentError, 'inbox_id is required for sandbox API' if sandbox && inbox_id.nil?
286
+ end
116
287
  end
117
288
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for Contact
5
+ # @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/220a54e31e5ca-contact
6
+ # @attr_reader id [String] The contact ID
7
+ # @attr_reader email [String] The contact's email address
8
+ # @attr_reader fields [Hash] Object of fields with merge tags
9
+ # @attr_reader list_ids [Array<Integer>] Array of list IDs
10
+ # @attr_reader status [String] The contact status (subscribed/unsubscribed)
11
+ # @attr_reader created_at [Integer] The creation timestamp
12
+ # @attr_reader updated_at [Integer] The last update timestamp
13
+ Contact = Struct.new(
14
+ :id,
15
+ :email,
16
+ :fields,
17
+ :list_ids,
18
+ :status,
19
+ :created_at,
20
+ :updated_at,
21
+ keyword_init: true
22
+ ) do
23
+ def initialize(options)
24
+ @action = options[:action]
25
+ super(options.except(:action))
26
+ end
27
+
28
+ # @return [Boolean] Whether the contact was newly created
29
+ def newly_created?
30
+ @action != 'updated'
31
+ end
32
+
33
+ # @return [Hash] The contact attributes as a hash
34
+ def to_h
35
+ super.compact
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for Contact Field
5
+ # @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/33efe96c91dcc-get-all-contact-fields
6
+ # @attr_reader id [Integer] The contact field ID
7
+ # @attr_reader name [String] The name of the contact field (max 80 characters)
8
+ # @attr_reader data_type [String] The data type of the field
9
+ # Allowed values: text, integer, float, boolean, date
10
+ # @attr_reader merge_tag [String] Personalize your campaigns by adding a merge tag.
11
+ # This field will be replaced with unique contact details for each recipient (max 80 characters)
12
+ ContactField = Struct.new(:id, :name, :data_type, :merge_tag, keyword_init: true) do
13
+ # @return [Hash] The contact field attributes as a hash
14
+ def to_h
15
+ super.compact
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'contact_field'
5
+
6
+ module Mailtrap
7
+ class ContactFieldsAPI
8
+ include BaseAPI
9
+
10
+ self.supported_options = %i[name data_type merge_tag]
11
+
12
+ self.response_class = ContactField
13
+
14
+ # Retrieves a specific contact field
15
+ # @param field_id [Integer] The contact field identifier
16
+ # @return [ContactField] Contact field object
17
+ # @!macro api_errors
18
+ def get(field_id)
19
+ base_get(field_id)
20
+ end
21
+
22
+ # Creates a new contact field
23
+ # @param [Hash] options The parameters to create
24
+ # @option options [String] :name The contact field name
25
+ # @option options [String] :data_type The data type of the field
26
+ # @option options [String] :merge_tag The merge tag of the field
27
+ # @return [ContactField] Created contact field object
28
+ # @!macro api_errors
29
+ # @raise [ArgumentError] If invalid options are provided
30
+ def create(options)
31
+ base_create(options)
32
+ end
33
+
34
+ # Updates an existing contact field
35
+ # @param field_id [Integer] The contact field ID
36
+ # @param [Hash] options The parameters to update
37
+ # @option options [String] :name The contact field name
38
+ # @option options [String] :merge_tag The merge tag of the field
39
+ # @return [ContactField] Updated contact field object
40
+ # @!macro api_errors
41
+ # @raise [ArgumentError] If invalid options are provided
42
+ def update(field_id, options)
43
+ base_update(field_id, options, %i[name merge_tag])
44
+ end
45
+
46
+ # Deletes a contact field
47
+ # @param field_id [Integer] The contact field ID
48
+ # @return nil
49
+ # @!macro api_errors
50
+ def delete(field_id)
51
+ base_delete(field_id)
52
+ end
53
+
54
+ # Lists all contact fields for the account
55
+ # @return [Array<ContactField>] Array of contact field objects
56
+ # @!macro api_errors
57
+ def list
58
+ base_list
59
+ end
60
+
61
+ private
62
+
63
+ def base_path
64
+ "/api/accounts/#{account_id}/contacts/fields"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for Contact List
5
+ # @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/6ec7a37234af2-contact-list
6
+ # @attr_reader id [Integer] The contact list ID
7
+ # @attr_reader name [String] The name of the contact list
8
+ ContactList = Struct.new(:id, :name, keyword_init: true) do
9
+ # @return [Hash] The contact list attributes as a hash
10
+ def to_h
11
+ super.compact
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'contact_list'
5
+
6
+ module Mailtrap
7
+ class ContactListsAPI
8
+ include BaseAPI
9
+
10
+ self.supported_options = %i[name]
11
+
12
+ self.response_class = ContactList
13
+
14
+ # Retrieves a specific contact list
15
+ # @param list_id [Integer] The contact list identifier
16
+ # @return [ContactList] Contact list object
17
+ # @!macro api_errors
18
+ def get(list_id)
19
+ base_get(list_id)
20
+ end
21
+
22
+ # Creates a new contact list
23
+ # @param [Hash] options The parameters to create
24
+ # @option options [String] :name The contact list name
25
+ # @return [ContactList] Created contact list object
26
+ # @!macro api_errors
27
+ # @raise [ArgumentError] If invalid options are provided
28
+ def create(options)
29
+ base_create(options)
30
+ end
31
+
32
+ # Updates an existing contact list
33
+ # @param list_id [Integer] The contact list ID
34
+ # @param [Hash] options The parameters to update
35
+ # @option options [String] :name The contact list name
36
+ # @return [ContactList] Updated contact list object
37
+ # @!macro api_errors
38
+ # @raise [ArgumentError] If invalid options are provided
39
+ def update(list_id, options)
40
+ base_update(list_id, options)
41
+ end
42
+
43
+ # Deletes a contact list
44
+ # @param list_id [Integer] The contact list ID
45
+ # @return nil
46
+ # @!macro api_errors
47
+ def delete(list_id)
48
+ base_delete(list_id)
49
+ end
50
+
51
+ # Lists all contact lists for the account
52
+ # @return [Array<ContactList>] Array of contact list objects
53
+ # @!macro api_errors
54
+ def list
55
+ base_list
56
+ end
57
+
58
+ private
59
+
60
+ def base_path
61
+ "/api/accounts/#{account_id}/contacts/lists"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_api'
4
+ require_relative 'contact'
5
+
6
+ module Mailtrap
7
+ class ContactsAPI
8
+ include BaseAPI
9
+
10
+ self.supported_options = %i[email fields list_ids]
11
+ self.response_class = Contact
12
+
13
+ # Retrieves a specific contact
14
+ # @param contact_id [String] The contact identifier, which can be either a UUID or an email address
15
+ # @return [Contact] Contact object
16
+ # @!macro api_errors
17
+ def get(contact_id)
18
+ base_get(contact_id)
19
+ end
20
+
21
+ # Creates a new contact
22
+ # @param [Hash] options The parameters to create
23
+ # @option options [String] :email The contact's email address
24
+ # @option options [Hash] :fields The contact's fields
25
+ # @option options [Array<Integer>] :list_ids The contact's list IDs
26
+ # @return [Contact] Created contact object
27
+ # @!macro api_errors
28
+ # @raise [ArgumentError] If invalid options are provided
29
+ def create(options)
30
+ base_create(options)
31
+ end
32
+
33
+ # Deletes a contact
34
+ # @param contact_id [String] The contact ID
35
+ # @return nil
36
+ # @!macro api_errors
37
+ def delete(contact_id)
38
+ base_delete(contact_id)
39
+ end
40
+
41
+ # Updates an existing contact or creates a new one if it doesn't exist
42
+ # @param contact_id [String] The contact ID or email address
43
+ # @param [Hash] options The parameters to update
44
+ # @option options [String] :email The contact's email address
45
+ # @option options [Hash] :fields The contact's fields
46
+ # @option options [Boolean] :unsubscribed Whether to unsubscribe the contact
47
+ # @return [Contact] Updated contact object
48
+ # @!macro api_errors
49
+ # @raise [ArgumentError] If invalid options are provided
50
+ def upsert(contact_id, options)
51
+ base_update(contact_id, options, %i[email fields unsubscribed])
52
+ end
53
+
54
+ # Adds a contact to specified lists
55
+ # @param contact_id [String] The contact ID or email address
56
+ # @param contact_list_ids [Array<Integer>] Array of list IDs to add the contact to
57
+ # @return [Contact] Updated contact object
58
+ # @!macro api_errors
59
+ def add_to_lists(contact_id, contact_list_ids = [])
60
+ update_lists(contact_id, list_ids_included: contact_list_ids)
61
+ end
62
+
63
+ # Removes a contact from specified lists
64
+ # @param contact_id [String] The contact ID or email address
65
+ # @param contact_list_ids [Array<Integer>] Array of list IDs to remove the contact from
66
+ # @return [Contact] Updated contact object
67
+ # @!macro api_errors
68
+ def remove_from_lists(contact_id, contact_list_ids = [])
69
+ update_lists(contact_id, list_ids_excluded: contact_list_ids)
70
+ end
71
+
72
+ private
73
+
74
+ def update_lists(contact_id, options)
75
+ base_update(contact_id, options, %i[list_ids_included list_ids_excluded])
76
+ end
77
+
78
+ def wrap_request(options)
79
+ { contact: options }
80
+ end
81
+
82
+ def handle_response(response)
83
+ response_class.new response[:data].slice(*response_class.members).merge(action: response[:action])
84
+ end
85
+
86
+ def base_path
87
+ "/api/accounts/#{account_id}/contacts"
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mailtrap
4
+ # Data Transfer Object for Email Template
5
+ #
6
+ # For field descriptions and response format, see the official API documentation:
7
+ # @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/9e5914d89c481-email-template
8
+ #
9
+ # @attr_reader id [Integer] The template ID
10
+ # @attr_reader uuid [String] The template UUID
11
+ # @attr_reader name [String] The template name
12
+ # @attr_reader subject [String] The email subject
13
+ # @attr_reader category [String] The template category
14
+ # @attr_reader body_html [String] The HTML content
15
+ # @attr_reader body_text [String] The plain text content
16
+ # @attr_reader created_at [String] The creation timestamp
17
+ # @attr_reader updated_at [String] The last update timestamp
18
+ EmailTemplate = Struct.new(
19
+ :id,
20
+ :uuid,
21
+ :name,
22
+ :subject,
23
+ :category,
24
+ :body_html,
25
+ :body_text,
26
+ :created_at,
27
+ :updated_at,
28
+ keyword_init: true
29
+ ) do
30
+ # @return [Hash] The template attributes as a hash
31
+ def to_h
32
+ super.compact
33
+ end
34
+ end
35
+ end