diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index 85ff38d40d88ddbc415d3bd3ea39867c00d1e2fd..b48146f5a0a08d5a26b9c0d425e0b079287c5198 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -64,6 +64,7 @@ aspect Analysis {
   }
 
   syn boolean EndpointTarget.entityIsNormalAttribute();
+  // TODO AttributeEndpointTarget.entityIsNormalAttribute
   eq TokenEndpointTarget.entityIsNormalAttribute() = !getToken().getNTA();
   eq TypeEndpointTarget.entityIsNormalAttribute() = !getType().getNTA();
   eq ContextFreeTypeEndpointTarget.entityIsNormalAttribute() = false;
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index bcd4cc4da4242ccd317f3d23bb66bb70a62f3599..b215f18142b88eb5280633fbc1e77edb628cd934 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -371,6 +371,10 @@ aspect MustacheReceiveAndSendAndHandleUri {
   syn String EndpointTarget.parentTypeName();
   syn String EndpointTarget.entityName();
 
+  // TODO AttributeEndpointTarget.getterName
+  eq AttributeEndpointTarget.parentTypeName() = getParentTypeDecl().getName();
+  eq AttributeEndpointTarget.entityName() = getName();
+
   eq TokenEndpointTarget.getterMethodName() = "get" + getToken().getName();
   eq TokenEndpointTarget.parentTypeName() = getToken().containingTypeDecl().getName();
   eq TokenEndpointTarget.entityName() = getToken().getName();
@@ -456,9 +460,7 @@ aspect MustacheSendDefinition {
           getTypeDecl().getName() :
           ragconnect().configJastAddList() + "<" + getTypeDecl().getName() + ">";
 
-  syn String EndpointTarget.senderName();
-  eq TokenEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getToken().getName();
-  eq TypeEndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + getType().getName();
+  syn String EndpointTarget.senderName() = ragconnect().internalRagConnectPrefix() + "_sender_" + entityName();
   eq ContextFreeTypeEndpointTarget.senderName() = null;
 
   syn String MEndpointDefinition.updateMethodName();
diff --git a/ragconnect.base/src/main/jastadd/Mappings.jrag b/ragconnect.base/src/main/jastadd/Mappings.jrag
index 738028b3a264a0c7dcf976618aa0b63f35be62c5..fb7c296cdd1c888488bda1a3ce3cb6399044de2e 100644
--- a/ragconnect.base/src/main/jastadd/Mappings.jrag
+++ b/ragconnect.base/src/main/jastadd/Mappings.jrag
@@ -191,6 +191,7 @@ aspect Mappings {
 
   // --- suitableReceiveDefaultMapping ---
   syn DefaultMappingDefinition EndpointDefinition.suitableReceiveDefaultMapping() {
+    // TODO might be expanded to AttributeEndpointTarget
     if (getEndpointTarget().isTypeEndpointTarget()) {
       try {
         TypeDecl typeDecl = program().resolveTypeDecl(targetTypeName());
@@ -236,6 +237,7 @@ aspect Mappings {
 
   // --- suitableSendDefaultMapping ---
   syn DefaultMappingDefinition EndpointDefinition.suitableSendDefaultMapping() {
+    // TODO might be expanded to AttributeEndpointTarget
     if (getEndpointTarget().isTypeEndpointTarget() && typeIsList() && !getIndexBasedListAccess()) {
       return ragconnect().defaultListTreeToBytesMapping();
     }
@@ -288,6 +290,7 @@ aspect Mappings {
     }
   }
   syn String EndpointTarget.targetTypeName();
+  eq AttributeEndpointTarget.targetTypeName() = getTypeName();
   eq TokenEndpointTarget.targetTypeName() = getToken().effectiveJavaTypeUse().getName();
   eq TypeEndpointTarget.targetTypeName() = getType().getTypeDecl().getName();
   eq ContextFreeTypeEndpointTarget.targetTypeName() = getTypeDecl().getName();
diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag
index a65ab72fa946bcec3351bb496f6632c012bac678..260ed5513f71ef4b8d2eade39cee9bc658886991 100644
--- a/ragconnect.base/src/main/jastadd/Navigation.jrag
+++ b/ragconnect.base/src/main/jastadd/Navigation.jrag
@@ -1,5 +1,6 @@
 aspect NewStuff {
 
+  // TODO regenerate for AttributeEndpointTarget
   /** Tests if EndpointTarget is a TokenEndpointTarget.
   *  @return 'true' if this is a TokenEndpointTarget, otherwise 'false'
   */
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index 01a0f2f884a2f0824bf2787eaf42d7d2d5797c41..f6cf0dd68708157aa291b6b0b50077b2fe72034c 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -13,9 +13,10 @@ TypeEndpointTarget : EndpointTarget;
 rel TypeEndpointTarget.Type <-> TypeComponent.TypeEndpointTarget*;
 ContextFreeTypeEndpointTarget : EndpointTarget;
 rel ContextFreeTypeEndpointTarget.TypeDecl <-> TypeDecl.ContextFreeTypeEndpointTarget*;
-UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName>;  // only used by parser
+UntypedEndpointTarget : EndpointTarget ::= <TypeName> <ChildName> <IsAttribute:boolean>;  // only used by parser
 // to be integrated:
-//AttributeEndpointTarget : EndpointTarget ::= <Name> ;
+AttributeEndpointTarget : EndpointTarget ::= <Name> <TypeName> ;
+rel AttributeEndpointTarget.ParentTypeDecl <-> TypeDecl.AttributeEndpointTarget*;
 //RelationEndpointTarget : EndpointTarget ;
 //rel RelationEndpointTarget.Role <-> Role.RelationEndpointTarget* ;
 
diff --git a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag
index f4a8912fe7c2affa7034d3eb8651f9d210bbba54..3de934126aa88278d14488ee61638841170c1f16 100644
--- a/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag
+++ b/ragconnect.base/src/main/jastadd/parser/ParserRewrites.jrag
@@ -23,6 +23,19 @@ aspect ParserRewrites {
       result.setTypeDecl(TypeDecl.createRef(getTypeName()));
       return result;
     }
+
+    when (getIsAttribute())
+    to AttributeEndpointTarget {
+      AttributeEndpointTarget result = new AttributeEndpointTarget();
+      String[] tokens = this.getChildName().split(":");
+      String attributeName = tokens[0];
+      String attributeTypeName = tokens[1];
+      result.copyOtherValuesFrom(this);
+      result.setName(attributeName);
+      result.setTypeName(attributeTypeName);
+      result.setParentTypeDecl(TypeDecl.createRef(getTypeName()));
+      return result;
+    }
   }
 
   syn String UntypedEndpointTarget.combinedName() = getTypeName() + "." + getChildName();
diff --git a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
index 54a388e51d35bc52296fd4f60e220d325be00674..dcd55d27ecaea7365205835d9eec1efcf6f59870 100644
--- a/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
+++ b/ragconnect.base/src/main/jastadd/parser/RagConnect.parser
@@ -62,8 +62,10 @@ EndpointDefinition endpoint_definition_type
 ;
 
 EndpointTarget endpoint_target
-  = ID.type_name DOT ID.child_name    {: return new UntypedEndpointTarget(type_name, child_name); :}
-  | ID.type_name                      {: return new UntypedEndpointTarget(type_name, ""); :}
+  = ID.type_name DOT ID.child_name    {: return new UntypedEndpointTarget(type_name, child_name, false); :}
+  | ID.type_name DOT ID.child_name BRACKETS COLON ID.attribute_type_name
+     {: return new UntypedEndpointTarget(type_name, child_name + ":" + attribute_type_name, true); :}
+  | ID.type_name                      {: return new UntypedEndpointTarget(type_name, "", false); :}
 ;
 
 ArrayList string_list
diff --git a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
index 8a1eaec1e8d95c0d8786f9f44168d1f060b27cfa..ffa057bc6df906f0a0226ba98bf01a7a6fbdbbf2 100644
--- a/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
+++ b/ragconnect.base/src/main/jastadd/scanner/Keywords.flex
@@ -8,3 +8,5 @@
 "with"       { return sym(Terminals.WITH); }
 "indexed"    { return sym(Terminals.INDEXED); }
 "add"        { return sym(Terminals.ADD); }
+"()"         { return sym(Terminals.BRACKETS); }
+":"          { return sym(Terminals.COLON); }
diff --git a/ragconnect.tests/build.gradle b/ragconnect.tests/build.gradle
index 8d073b55ac6b14ebdf6b295bd993dda64dbbd701..b545b19b84d42435217f239c5ae5dd6d34604e48 100644
--- a/ragconnect.tests/build.gradle
+++ b/ragconnect.tests/build.gradle
@@ -621,3 +621,29 @@ task compileIndexedSendIncremental(type: RagConnectTest, dependsOn: ':ragconnect
         extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
     }
 }
+
+// --- Test: attribute-incremental ---
+task compileAttributeIncremental(type: RagConnectTest, dependsOn: ':ragconnect.base:jar') {
+    ragconnect {
+        outputDir = file('src/test/02-after-ragconnect/attributeInc')
+        inputFiles = [file('src/test/01-input/attribute/Test.relast'),
+                      file('src/test/01-input/attribute/Test.connect')]
+        rootNode = 'Root'
+        logWrites = true
+        logReads = true
+        logIncremental = true
+        extraOptions = ['--experimental-jastadd-329']
+    }
+    relast {
+        useJastAddNames = true
+        grammarName = 'src/test/03-after-relast/attributeInc/attributeInc'
+        serializer = 'jackson'
+    }
+    jastadd {
+        jastAddList = 'JastAddList'
+        packageName = 'attributeInc.ast'
+        inputFiles = [file('src/test/01-input/attribute/Test.jadd')]
+        extraOptions = JASTADD_INCREMENTAL_OPTIONS_TRACING_FULL
+    }
+}
+compileAttributeIncremental.outputs.upToDateWhen { false }
diff --git a/ragconnect.tests/src/test/01-input/attribute/README.md b/ragconnect.tests/src/test/01-input/attribute/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..08481b7a25582721dfa8435ac199b94acb205de6
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/README.md
@@ -0,0 +1,3 @@
+# Attribute
+
+Idea: Use send definitions for attributes.
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.connect b/ragconnect.tests/src/test/01-input/attribute/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..3e6a66ebabdac4e4a7072395519b7d9fd4ab48e8
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.connect
@@ -0,0 +1,31 @@
+send SenderRoot.basic():String ;
+send SenderRoot.simple():String ;
+send SenderRoot.transformed():int ;
+send SenderRoot.toReferenceType():A ;
+send SenderRoot.toNTA():A ;
+
+AddSuffix maps A a to A {:
+  A result = new A();
+  String changedValue = a.getValue() + "post";
+  result.setValue(changedValue);
+  result.setInner(new Inner("inner" + changedValue));
+  return result;
+:}
+
+AddStringSuffix maps String s to String {:
+  return s + "post";
+:}
+
+AddPlusOne maps int i to int {:
+  return i + 1;
+:}
+
+receive ReceiverRoot.FromBasic;
+receive ReceiverRoot.FromSimpleNoMapping;
+receive ReceiverRoot.FromSimpleWithMapping using AddStringSuffix;
+receive ReceiverRoot.FromTransformedNoMapping;
+receive ReceiverRoot.FromTransformedWithMapping using AddPlusOne;
+receive ReceiverRoot.FromReferenceTypeNoMapping;
+receive ReceiverRoot.FromReferenceTypeWithMapping using AddSuffix;
+receive ReceiverRoot.FromNTANoMapping;
+receive ReceiverRoot.FromNTAWithMapping using AddSuffix;
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.jadd b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..c5274c6f73b0a49d77443522d9596cd8e8b5f858
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.jadd
@@ -0,0 +1,39 @@
+aspect Computation {
+  syn String SenderRoot.basic() = getInput();
+  syn String SenderRoot.simple() = getInput() + "Post";
+  syn int SenderRoot.transformed() = getInput().length();
+  syn A SenderRoot.toReferenceType() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("1");
+    result.setInner(inner);
+    return result;
+  }
+  syn nta A SenderRoot.toNTA() {
+    A result = new A();
+    result.setValue(getInput());
+    Inner inner = new Inner();
+    inner.setInnerValue("2");
+    result.setInner(inner);
+    return result;
+  }
+}
+aspect MakeCodeCompile {
+
+}
+aspect MakeCodeWork {
+
+}
+aspect NameResolution {
+  // overriding customID guarantees to produce the same JSON representation for equal lists
+  // otherwise, the value for id is different each time
+  @Override
+  protected String A.customID() {
+    return getClass().getSimpleName() + getValue();
+  }
+  @Override
+  protected String Inner.customID() {
+    return getClass().getSimpleName() + getInnerValue();
+  }
+}
diff --git a/ragconnect.tests/src/test/01-input/attribute/Test.relast b/ragconnect.tests/src/test/01-input/attribute/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..d7edb5c53ae2b4e3380b73e0800395ae26eaae41
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/attribute/Test.relast
@@ -0,0 +1,14 @@
+Root ::= SenderRoot ReceiverRoot;
+SenderRoot ::= <Input> ;
+ReceiverRoot ::=
+    <FromBasic>
+    <FromSimpleNoMapping>
+    <FromSimpleWithMapping>
+    <FromTransformedNoMapping:int>
+    <FromTransformedWithMapping:int>
+    FromReferenceTypeNoMapping:A
+    FromReferenceTypeWithMapping:A
+    FromNTANoMapping:A
+    FromNTAWithMapping:A ;
+A ::= <Value> Inner ;
+Inner ::= <InnerValue> ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b877447b20b78b9e6413afde73abb8670a4473c
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AttributeTest.java
@@ -0,0 +1,48 @@
+package org.jastadd.ragconnect.tests;
+
+import attribute.ast.*;
+import org.junit.jupiter.api.Tag;
+
+import java.io.IOException;
+
+/**
+ * Test case "attribute".
+ *
+ * @author rschoene - Initial contribution
+ */
+@Tag("Incremental")
+@Tag("New")
+public class AttributeTest extends AbstractMqttTest {
+  Root model;
+  MqttHandler handler;
+
+  @Override
+  protected void createModel() {
+
+  }
+
+  @Override
+  protected void setupReceiverAndConnect() throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  protected void communicateSendInitialValue() throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  protected void communicateOnlyUpdatedValue() throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  protected void closeConnections() {
+    if (handler != null) {
+      handler.close();
+    }
+    if (model != null) {
+      model.ragconnectCloseConnections();
+    }
+  }
+}